blob: 78389c5121f21298667c93130351ee22233d7868 [file] [log] [blame]
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001/*
2 * Copyright (C) 2013 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.internal.inputmethod;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageManager;
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +090023import android.content.pm.PackageManager.NameNotFoundException;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090024import android.content.res.Resources;
25import android.provider.Settings;
26import android.provider.Settings.SettingNotFoundException;
27import android.text.TextUtils;
28import android.util.Pair;
29import android.util.Slog;
30import android.view.inputmethod.InputMethodInfo;
31import android.view.inputmethod.InputMethodSubtype;
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +090032import android.view.textservice.SpellCheckerInfo;
33import android.view.textservice.TextServicesManager;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090034
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Locale;
39
40/**
41 * InputMethodManagerUtils contains some static methods that provides IME informations.
42 * This methods are supposed to be used in both the framework and the Settings application.
43 */
44public class InputMethodUtils {
45 public static final boolean DEBUG = false;
46 public static final int NOT_A_SUBTYPE_ID = -1;
47 public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
48 public static final String SUBTYPE_MODE_VOICE = "voice";
49 private static final String TAG = "InputMethodUtils";
50 private static final Locale ENGLISH_LOCALE = new Locale("en");
51 private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
52 private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
53 "EnabledWhenDefaultIsNotAsciiCapable";
54 private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
55
56 private InputMethodUtils() {
57 // This utility class is not publicly instantiable.
58 }
59
60 public static boolean isSystemIme(InputMethodInfo inputMethod) {
61 return (inputMethod.getServiceInfo().applicationInfo.flags
62 & ApplicationInfo.FLAG_SYSTEM) != 0;
63 }
64
Satoshi Kataokaf1367b72013-01-25 17:20:12 +090065 public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090066 if (!isSystemIme(imi)) {
67 return false;
68 }
Satoshi Kataokaf1367b72013-01-25 17:20:12 +090069 return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
70 }
71
72 private static boolean isSystemAuxilialyImeThatHashAutomaticSubtype(InputMethodInfo imi) {
73 if (!isSystemIme(imi)) {
74 return false;
75 }
76 if (!imi.isAuxiliaryIme()) {
77 return false;
78 }
79 final int subtypeCount = imi.getSubtypeCount();
80 for (int i = 0; i < subtypeCount; ++i) {
81 final InputMethodSubtype s = imi.getSubtypeAt(i);
82 if (s.overridesImplicitlyEnabledSubtype()) {
83 return true;
84 }
85 }
86 return false;
87 }
88
89 public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
90 Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
91 final ArrayList<InputMethodInfo> retval = new ArrayList<InputMethodInfo>();
92 boolean auxilialyImeAdded = false;
93 for (int i = 0; i < imis.size(); ++i) {
94 final InputMethodInfo imi = imis.get(i);
95 if (isDefaultEnabledIme(isSystemReady, imi, context)) {
96 retval.add(imi);
97 if (imi.isAuxiliaryIme()) {
98 auxilialyImeAdded = true;
99 }
100 }
101 }
102 if (auxilialyImeAdded) {
103 return retval;
104 }
105 for (int i = 0; i < imis.size(); ++i) {
106 final InputMethodInfo imi = imis.get(i);
107 if (isSystemAuxilialyImeThatHashAutomaticSubtype(imi)) {
108 retval.add(imi);
109 }
110 }
111 return retval;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900112 }
113
114 // TODO: Rename isSystemDefaultImeThatHasCurrentLanguageSubtype
115 public static boolean isValidSystemDefaultIme(
116 boolean isSystemReady, InputMethodInfo imi, Context context) {
117 if (!isSystemReady) {
118 return false;
119 }
120 if (!isSystemIme(imi)) {
121 return false;
122 }
123 if (imi.getIsDefaultResourceId() != 0) {
124 try {
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900125 if (imi.isDefault(context) && containsSubtypeOf(
126 imi, context.getResources().getConfiguration().locale.getLanguage(),
127 null /* mode */)) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900128 return true;
129 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900130 } catch (Resources.NotFoundException ex) {
131 }
132 }
133 if (imi.getSubtypeCount() == 0) {
134 Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
135 }
136 return false;
137 }
138
139 public static boolean isDefaultEnabledIme(
140 boolean isSystemReady, InputMethodInfo imi, Context context) {
141 return isValidSystemDefaultIme(isSystemReady, imi, context)
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900142 || isSystemImeThatHasEnglishKeyboardSubtype(imi);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900143 }
144
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900145 private static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900146 final int N = imi.getSubtypeCount();
147 for (int i = 0; i < N; ++i) {
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900148 if (!imi.getSubtypeAt(i).getLocale().startsWith(language)) {
149 continue;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900150 }
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900151 if(!TextUtils.isEmpty(mode) && !imi.getSubtypeAt(i).getMode().equalsIgnoreCase(mode)) {
152 continue;
153 }
154 return true;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900155 }
156 return false;
157 }
158
159 public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
160 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
161 final int subtypeCount = imi.getSubtypeCount();
162 for (int i = 0; i < subtypeCount; ++i) {
163 subtypes.add(imi.getSubtypeAt(i));
164 }
165 return subtypes;
166 }
167
168 public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
169 InputMethodInfo imi, String mode) {
170 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
171 final int subtypeCount = imi.getSubtypeCount();
172 for (int i = 0; i < subtypeCount; ++i) {
173 final InputMethodSubtype subtype = imi.getSubtypeAt(i);
174 if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
175 subtypes.add(subtype);
176 }
177 }
178 return subtypes;
179 }
180
181 public static InputMethodInfo getMostApplicableDefaultIME(
182 List<InputMethodInfo> enabledImes) {
183 if (enabledImes != null && enabledImes.size() > 0) {
184 // We'd prefer to fall back on a system IME, since that is safer.
185 int i = enabledImes.size();
186 int firstFoundSystemIme = -1;
187 while (i > 0) {
188 i--;
189 final InputMethodInfo imi = enabledImes.get(i);
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900190 if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900191 && !imi.isAuxiliaryIme()) {
192 return imi;
193 }
194 if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
195 && !imi.isAuxiliaryIme()) {
196 firstFoundSystemIme = i;
197 }
198 }
199 return enabledImes.get(Math.max(firstFoundSystemIme, 0));
200 }
201 return null;
202 }
203
204 public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
205 return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
206 }
207
208 public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
209 if (imi != null) {
210 final int subtypeCount = imi.getSubtypeCount();
211 for (int i = 0; i < subtypeCount; ++i) {
212 InputMethodSubtype ims = imi.getSubtypeAt(i);
213 if (subtypeHashCode == ims.hashCode()) {
214 return i;
215 }
216 }
217 }
218 return NOT_A_SUBTYPE_ID;
219 }
220
221 private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
222 Resources res, InputMethodInfo imi) {
223 final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
224 final String systemLocale = res.getConfiguration().locale.toString();
225 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
226 final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
227 new HashMap<String, InputMethodSubtype>();
228 final int N = subtypes.size();
229 for (int i = 0; i < N; ++i) {
230 // scan overriding implicitly enabled subtypes.
231 InputMethodSubtype subtype = subtypes.get(i);
232 if (subtype.overridesImplicitlyEnabledSubtype()) {
233 final String mode = subtype.getMode();
234 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
235 applicableModeAndSubtypesMap.put(mode, subtype);
236 }
237 }
238 }
239 if (applicableModeAndSubtypesMap.size() > 0) {
240 return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
241 }
242 for (int i = 0; i < N; ++i) {
243 final InputMethodSubtype subtype = subtypes.get(i);
244 final String locale = subtype.getLocale();
245 final String mode = subtype.getMode();
246 // When system locale starts with subtype's locale, that subtype will be applicable
247 // for system locale
248 // For instance, it's clearly applicable for cases like system locale = en_US and
249 // subtype = en, but it is not necessarily considered applicable for cases like system
250 // locale = en and subtype = en_US.
251 // We just call systemLocale.startsWith(locale) in this function because there is no
252 // need to find applicable subtypes aggressively unlike
253 // findLastResortApplicableSubtypeLocked.
254 if (systemLocale.startsWith(locale)) {
255 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
256 // If more applicable subtypes are contained, skip.
257 if (applicableSubtype != null) {
258 if (systemLocale.equals(applicableSubtype.getLocale())) continue;
259 if (!systemLocale.equals(locale)) continue;
260 }
261 applicableModeAndSubtypesMap.put(mode, subtype);
262 }
263 }
264 final InputMethodSubtype keyboardSubtype
265 = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
266 final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
267 applicableModeAndSubtypesMap.values());
268 if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
269 for (int i = 0; i < N; ++i) {
270 final InputMethodSubtype subtype = subtypes.get(i);
271 final String mode = subtype.getMode();
272 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
273 TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
274 applicableSubtypes.add(subtype);
275 }
276 }
277 }
278 if (keyboardSubtype == null) {
279 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
280 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
281 if (lastResortKeyboardSubtype != null) {
282 applicableSubtypes.add(lastResortKeyboardSubtype);
283 }
284 }
285 return applicableSubtypes;
286 }
287
288 private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
289 Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes,
290 boolean allowsImplicitlySelectedSubtypes) {
291 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
292 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
293 context.getResources(), imi);
294 }
295 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
296 }
297
298 /**
299 * If there are no selected subtypes, tries finding the most applicable one according to the
300 * given locale.
301 * @param subtypes this function will search the most applicable subtype in subtypes
302 * @param mode subtypes will be filtered by mode
303 * @param locale subtypes will be filtered by locale
304 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
305 * it will return the first subtype matched with mode
306 * @return the most applicable subtypeId
307 */
308 public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
309 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
310 boolean canIgnoreLocaleAsLastResort) {
311 if (subtypes == null || subtypes.size() == 0) {
312 return null;
313 }
314 if (TextUtils.isEmpty(locale)) {
315 locale = res.getConfiguration().locale.toString();
316 }
317 final String language = locale.substring(0, 2);
318 boolean partialMatchFound = false;
319 InputMethodSubtype applicableSubtype = null;
320 InputMethodSubtype firstMatchedModeSubtype = null;
321 final int N = subtypes.size();
322 for (int i = 0; i < N; ++i) {
323 InputMethodSubtype subtype = subtypes.get(i);
324 final String subtypeLocale = subtype.getLocale();
325 // An applicable subtype should match "mode". If mode is null, mode will be ignored,
326 // and all subtypes with all modes can be candidates.
327 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
328 if (firstMatchedModeSubtype == null) {
329 firstMatchedModeSubtype = subtype;
330 }
331 if (locale.equals(subtypeLocale)) {
332 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
333 applicableSubtype = subtype;
334 break;
335 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
336 // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
337 applicableSubtype = subtype;
338 partialMatchFound = true;
339 }
340 }
341 }
342
343 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
344 return firstMatchedModeSubtype;
345 }
346
347 // The first subtype applicable to the system locale will be defined as the most applicable
348 // subtype.
349 if (DEBUG) {
350 if (applicableSubtype != null) {
351 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
352 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
353 }
354 }
355 return applicableSubtype;
356 }
357
358 public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
359 if (subtype == null) return true;
360 return !subtype.isAuxiliary();
361 }
362
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900363 public static void setNonSelectedSystemImesDisabledUntilUsed(
364 PackageManager packageManager, List<InputMethodInfo> enabledImis) {
365 if (DEBUG) {
366 Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
367 }
368 final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
369 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
370 if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
371 return;
372 }
373 // Only the current spell checker should be treated as an enabled one.
374 final SpellCheckerInfo currentSpellChecker =
375 TextServicesManager.getInstance().getCurrentSpellChecker();
376 for (final String packageName : systemImesDisabledUntilUsed) {
377 if (DEBUG) {
378 Slog.d(TAG, "check " + packageName);
379 }
380 boolean enabledIme = false;
381 for (int j = 0; j < enabledImis.size(); ++j) {
382 final InputMethodInfo imi = enabledImis.get(j);
383 if (packageName.equals(imi.getPackageName())) {
384 enabledIme = true;
385 break;
386 }
387 }
388 if (enabledIme) {
389 // enabled ime. skip
390 continue;
391 }
392 if (currentSpellChecker != null
393 && packageName.equals(currentSpellChecker.getPackageName())) {
394 // enabled spell checker. skip
395 if (DEBUG) {
396 Slog.d(TAG, packageName + " is the current spell checker. skip");
397 }
398 continue;
399 }
400 ApplicationInfo ai = null;
401 try {
402 ai = packageManager.getApplicationInfo(packageName,
403 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
404 } catch (NameNotFoundException e) {
405 Slog.w(TAG, "NameNotFoundException: " + packageName, e);
406 }
407 if (ai == null) {
408 // No app found for packageName
409 continue;
410 }
411 final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
412 if (!isSystemPackage) {
413 continue;
414 }
415 setDisabledUntilUsed(packageManager, packageName);
416 }
417 }
418
419 private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) {
420 final int state = packageManager.getApplicationEnabledSetting(packageName);
421 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
422 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
423 if (DEBUG) {
424 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
425 }
426 packageManager.setApplicationEnabledSetting(packageName,
427 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0);
428 } else {
429 if (DEBUG) {
430 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
431 }
432 }
433 }
434
Satoshi Kataokab2827262013-07-04 19:43:14 +0900435 public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
436 InputMethodSubtype subtype) {
437 final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
438 return subtype != null
439 ? TextUtils.concat(subtype.getDisplayName(context,
440 imi.getPackageName(), imi.getServiceInfo().applicationInfo),
441 (TextUtils.isEmpty(imiLabel) ?
442 "" : " - " + imiLabel))
443 : imiLabel;
444 }
445
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900446 /**
447 * Utility class for putting and getting settings for InputMethod
448 * TODO: Move all putters and getters of settings to this class.
449 */
450 public static class InputMethodSettings {
451 // The string for enabled input method is saved as follows:
452 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
453 private static final char INPUT_METHOD_SEPARATER = ':';
454 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
455 private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
456 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
457
458 private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
459 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
460
461 private final Resources mRes;
462 private final ContentResolver mResolver;
463 private final HashMap<String, InputMethodInfo> mMethodMap;
464 private final ArrayList<InputMethodInfo> mMethodList;
465
466 private String mEnabledInputMethodsStrCache;
467 private int mCurrentUserId;
468
469 private static void buildEnabledInputMethodsSettingString(
470 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
471 String id = pair.first;
472 ArrayList<String> subtypes = pair.second;
473 builder.append(id);
474 // Inputmethod and subtypes are saved in the settings as follows:
475 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
476 for (String subtypeId: subtypes) {
477 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
478 }
479 }
480
481 public InputMethodSettings(
482 Resources res, ContentResolver resolver,
483 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
484 int userId) {
485 setCurrentUserId(userId);
486 mRes = res;
487 mResolver = resolver;
488 mMethodMap = methodMap;
489 mMethodList = methodList;
490 }
491
492 public void setCurrentUserId(int userId) {
493 if (DEBUG) {
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900494 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900495 }
496 // IMMS settings are kept per user, so keep track of current user
497 mCurrentUserId = userId;
498 }
499
500 public List<InputMethodInfo> getEnabledInputMethodListLocked() {
501 return createEnabledInputMethodListLocked(
502 getEnabledInputMethodsAndSubtypeListLocked());
503 }
504
505 public List<Pair<InputMethodInfo, ArrayList<String>>>
506 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
507 return createEnabledInputMethodAndSubtypeHashCodeListLocked(
508 getEnabledInputMethodsAndSubtypeListLocked());
509 }
510
511 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
512 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
513 List<InputMethodSubtype> enabledSubtypes =
514 getEnabledInputMethodSubtypeListLocked(imi);
515 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
516 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
517 context.getResources(), imi);
518 }
519 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
520 }
521
522 private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
523 InputMethodInfo imi) {
524 List<Pair<String, ArrayList<String>>> imsList =
525 getEnabledInputMethodsAndSubtypeListLocked();
526 ArrayList<InputMethodSubtype> enabledSubtypes =
527 new ArrayList<InputMethodSubtype>();
528 if (imi != null) {
529 for (Pair<String, ArrayList<String>> imsPair : imsList) {
530 InputMethodInfo info = mMethodMap.get(imsPair.first);
531 if (info != null && info.getId().equals(imi.getId())) {
532 final int subtypeCount = info.getSubtypeCount();
533 for (int i = 0; i < subtypeCount; ++i) {
534 InputMethodSubtype ims = info.getSubtypeAt(i);
535 for (String s: imsPair.second) {
536 if (String.valueOf(ims.hashCode()).equals(s)) {
537 enabledSubtypes.add(ims);
538 }
539 }
540 }
541 break;
542 }
543 }
544 }
545 return enabledSubtypes;
546 }
547
548 // At the initial boot, the settings for input methods are not set,
549 // so we need to enable IME in that case.
550 public void enableAllIMEsIfThereIsNoEnabledIME() {
551 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
552 StringBuilder sb = new StringBuilder();
553 final int N = mMethodList.size();
554 for (int i = 0; i < N; i++) {
555 InputMethodInfo imi = mMethodList.get(i);
556 Slog.i(TAG, "Adding: " + imi.getId());
557 if (i > 0) sb.append(':');
558 sb.append(imi.getId());
559 }
560 putEnabledInputMethodsStr(sb.toString());
561 }
562 }
563
564 public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
565 ArrayList<Pair<String, ArrayList<String>>> imsList
566 = new ArrayList<Pair<String, ArrayList<String>>>();
567 final String enabledInputMethodsStr = getEnabledInputMethodsStr();
568 if (TextUtils.isEmpty(enabledInputMethodsStr)) {
569 return imsList;
570 }
571 mInputMethodSplitter.setString(enabledInputMethodsStr);
572 while (mInputMethodSplitter.hasNext()) {
573 String nextImsStr = mInputMethodSplitter.next();
574 mSubtypeSplitter.setString(nextImsStr);
575 if (mSubtypeSplitter.hasNext()) {
576 ArrayList<String> subtypeHashes = new ArrayList<String>();
577 // The first element is ime id.
578 String imeId = mSubtypeSplitter.next();
579 while (mSubtypeSplitter.hasNext()) {
580 subtypeHashes.add(mSubtypeSplitter.next());
581 }
582 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
583 }
584 }
585 return imsList;
586 }
587
588 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
589 if (reloadInputMethodStr) {
590 getEnabledInputMethodsStr();
591 }
592 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
593 // Add in the newly enabled input method.
594 putEnabledInputMethodsStr(id);
595 } else {
596 putEnabledInputMethodsStr(
597 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
598 }
599 }
600
601 /**
602 * Build and put a string of EnabledInputMethods with removing specified Id.
603 * @return the specified id was removed or not.
604 */
605 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
606 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
607 boolean isRemoved = false;
608 boolean needsAppendSeparator = false;
609 for (Pair<String, ArrayList<String>> ims: imsList) {
610 String curId = ims.first;
611 if (curId.equals(id)) {
612 // We are disabling this input method, and it is
613 // currently enabled. Skip it to remove from the
614 // new list.
615 isRemoved = true;
616 } else {
617 if (needsAppendSeparator) {
618 builder.append(INPUT_METHOD_SEPARATER);
619 } else {
620 needsAppendSeparator = true;
621 }
622 buildEnabledInputMethodsSettingString(builder, ims);
623 }
624 }
625 if (isRemoved) {
626 // Update the setting with the new list of input methods.
627 putEnabledInputMethodsStr(builder.toString());
628 }
629 return isRemoved;
630 }
631
632 private List<InputMethodInfo> createEnabledInputMethodListLocked(
633 List<Pair<String, ArrayList<String>>> imsList) {
634 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
635 for (Pair<String, ArrayList<String>> ims: imsList) {
636 InputMethodInfo info = mMethodMap.get(ims.first);
637 if (info != null) {
638 res.add(info);
639 }
640 }
641 return res;
642 }
643
644 private List<Pair<InputMethodInfo, ArrayList<String>>>
645 createEnabledInputMethodAndSubtypeHashCodeListLocked(
646 List<Pair<String, ArrayList<String>>> imsList) {
647 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
648 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
649 for (Pair<String, ArrayList<String>> ims : imsList) {
650 InputMethodInfo info = mMethodMap.get(ims.first);
651 if (info != null) {
652 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
653 }
654 }
655 return res;
656 }
657
658 private void putEnabledInputMethodsStr(String str) {
659 Settings.Secure.putStringForUser(
660 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
661 mEnabledInputMethodsStrCache = str;
662 if (DEBUG) {
663 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
664 }
665 }
666
Dianne Hackbornfd7aded2013-01-22 17:10:23 -0800667 public String getEnabledInputMethodsStr() {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900668 mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
669 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
670 if (DEBUG) {
671 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
672 + ", " + mCurrentUserId);
673 }
674 return mEnabledInputMethodsStrCache;
675 }
676
677 private void saveSubtypeHistory(
678 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
679 StringBuilder builder = new StringBuilder();
680 boolean isImeAdded = false;
681 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
682 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
683 newSubtypeId);
684 isImeAdded = true;
685 }
686 for (Pair<String, String> ime: savedImes) {
687 String imeId = ime.first;
688 String subtypeId = ime.second;
689 if (TextUtils.isEmpty(subtypeId)) {
690 subtypeId = NOT_A_SUBTYPE_ID_STR;
691 }
692 if (isImeAdded) {
693 builder.append(INPUT_METHOD_SEPARATER);
694 } else {
695 isImeAdded = true;
696 }
697 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
698 subtypeId);
699 }
700 // Remove the last INPUT_METHOD_SEPARATER
701 putSubtypeHistoryStr(builder.toString());
702 }
703
704 private void addSubtypeToHistory(String imeId, String subtypeId) {
705 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
706 for (Pair<String, String> ime: subtypeHistory) {
707 if (ime.first.equals(imeId)) {
708 if (DEBUG) {
709 Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
710 + ime.second);
711 }
712 // We should break here
713 subtypeHistory.remove(ime);
714 break;
715 }
716 }
717 if (DEBUG) {
718 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
719 }
720 saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
721 }
722
723 private void putSubtypeHistoryStr(String str) {
724 if (DEBUG) {
725 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
726 }
727 Settings.Secure.putStringForUser(
728 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
729 }
730
731 public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
732 // Gets the first one from the history
733 return getLastSubtypeForInputMethodLockedInternal(null);
734 }
735
736 public String getLastSubtypeForInputMethodLocked(String imeId) {
737 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
738 if (ime != null) {
739 return ime.second;
740 } else {
741 return null;
742 }
743 }
744
745 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
746 List<Pair<String, ArrayList<String>>> enabledImes =
747 getEnabledInputMethodsAndSubtypeListLocked();
748 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
749 for (Pair<String, String> imeAndSubtype : subtypeHistory) {
750 final String imeInTheHistory = imeAndSubtype.first;
751 // If imeId is empty, returns the first IME and subtype in the history
752 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
753 final String subtypeInTheHistory = imeAndSubtype.second;
754 final String subtypeHashCode =
755 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
756 enabledImes, imeInTheHistory, subtypeInTheHistory);
757 if (!TextUtils.isEmpty(subtypeHashCode)) {
758 if (DEBUG) {
759 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
760 }
761 return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
762 }
763 }
764 }
765 if (DEBUG) {
766 Slog.d(TAG, "No enabled IME found in the history");
767 }
768 return null;
769 }
770
771 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
772 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
773 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
774 if (enabledIme.first.equals(imeId)) {
775 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
776 final InputMethodInfo imi = mMethodMap.get(imeId);
777 if (explicitlyEnabledSubtypes.size() == 0) {
778 // If there are no explicitly enabled subtypes, applicable subtypes are
779 // enabled implicitly.
780 // If IME is enabled and no subtypes are enabled, applicable subtypes
781 // are enabled implicitly, so needs to treat them to be enabled.
782 if (imi != null && imi.getSubtypeCount() > 0) {
783 List<InputMethodSubtype> implicitlySelectedSubtypes =
784 getImplicitlyApplicableSubtypesLocked(mRes, imi);
785 if (implicitlySelectedSubtypes != null) {
786 final int N = implicitlySelectedSubtypes.size();
787 for (int i = 0; i < N; ++i) {
788 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
789 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
790 return subtypeHashCode;
791 }
792 }
793 }
794 }
795 } else {
796 for (String s: explicitlyEnabledSubtypes) {
797 if (s.equals(subtypeHashCode)) {
798 // If both imeId and subtypeId are enabled, return subtypeId.
799 try {
800 final int hashCode = Integer.valueOf(subtypeHashCode);
801 // Check whether the subtype id is valid or not
802 if (isValidSubtypeId(imi, hashCode)) {
803 return s;
804 } else {
805 return NOT_A_SUBTYPE_ID_STR;
806 }
807 } catch (NumberFormatException e) {
808 return NOT_A_SUBTYPE_ID_STR;
809 }
810 }
811 }
812 }
813 // If imeId was enabled but subtypeId was disabled.
814 return NOT_A_SUBTYPE_ID_STR;
815 }
816 }
817 // If both imeId and subtypeId are disabled, return null
818 return null;
819 }
820
821 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
822 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
823 final String subtypeHistoryStr = getSubtypeHistoryStr();
824 if (TextUtils.isEmpty(subtypeHistoryStr)) {
825 return imsList;
826 }
827 mInputMethodSplitter.setString(subtypeHistoryStr);
828 while (mInputMethodSplitter.hasNext()) {
829 String nextImsStr = mInputMethodSplitter.next();
830 mSubtypeSplitter.setString(nextImsStr);
831 if (mSubtypeSplitter.hasNext()) {
832 String subtypeId = NOT_A_SUBTYPE_ID_STR;
833 // The first element is ime id.
834 String imeId = mSubtypeSplitter.next();
835 while (mSubtypeSplitter.hasNext()) {
836 subtypeId = mSubtypeSplitter.next();
837 break;
838 }
839 imsList.add(new Pair<String, String>(imeId, subtypeId));
840 }
841 }
842 return imsList;
843 }
844
845 private String getSubtypeHistoryStr() {
846 if (DEBUG) {
847 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
848 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
849 }
850 return Settings.Secure.getStringForUser(
851 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
852 }
853
854 public void putSelectedInputMethod(String imeId) {
855 if (DEBUG) {
856 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
857 + mCurrentUserId);
858 }
859 Settings.Secure.putStringForUser(
860 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
861 }
862
863 public void putSelectedSubtype(int subtypeId) {
864 if (DEBUG) {
865 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
866 + mCurrentUserId);
867 }
868 Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
869 subtypeId, mCurrentUserId);
870 }
871
872 public String getDisabledSystemInputMethods() {
873 return Settings.Secure.getStringForUser(
874 mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
875 }
876
877 public String getSelectedInputMethod() {
878 if (DEBUG) {
879 Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
880 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
881 + ", " + mCurrentUserId);
882 }
883 return Settings.Secure.getStringForUser(
884 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
885 }
886
887 public boolean isSubtypeSelected() {
888 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
889 }
890
891 private int getSelectedInputMethodSubtypeHashCode() {
892 try {
893 return Settings.Secure.getIntForUser(
894 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
895 } catch (SettingNotFoundException e) {
896 return NOT_A_SUBTYPE_ID;
897 }
898 }
899
900 public int getCurrentUserId() {
901 return mCurrentUserId;
902 }
903
904 public int getSelectedInputMethodSubtypeId(String selectedImiId) {
905 final InputMethodInfo imi = mMethodMap.get(selectedImiId);
906 if (imi == null) {
907 return NOT_A_SUBTYPE_ID;
908 }
909 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
910 return getSubtypeIdFromHashCode(imi, subtypeHashCode);
911 }
912
913 public void saveCurrentInputMethodAndSubtypeToHistory(
914 String curMethodId, InputMethodSubtype currentSubtype) {
915 String subtypeId = NOT_A_SUBTYPE_ID_STR;
916 if (currentSubtype != null) {
917 subtypeId = String.valueOf(currentSubtype.hashCode());
918 }
919 if (canAddToLastInputMethod(currentSubtype)) {
920 addSubtypeToHistory(curMethodId, subtypeId);
921 }
922 }
923 }
924}