blob: 4e213247e50320ac51460af00b54a1b276424733 [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 Kataoka8e303cc2013-01-11 15:55:28 +0900435 /**
436 * Utility class for putting and getting settings for InputMethod
437 * TODO: Move all putters and getters of settings to this class.
438 */
439 public static class InputMethodSettings {
440 // The string for enabled input method is saved as follows:
441 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
442 private static final char INPUT_METHOD_SEPARATER = ':';
443 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
444 private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
445 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
446
447 private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
448 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
449
450 private final Resources mRes;
451 private final ContentResolver mResolver;
452 private final HashMap<String, InputMethodInfo> mMethodMap;
453 private final ArrayList<InputMethodInfo> mMethodList;
454
455 private String mEnabledInputMethodsStrCache;
456 private int mCurrentUserId;
457
458 private static void buildEnabledInputMethodsSettingString(
459 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
460 String id = pair.first;
461 ArrayList<String> subtypes = pair.second;
462 builder.append(id);
463 // Inputmethod and subtypes are saved in the settings as follows:
464 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
465 for (String subtypeId: subtypes) {
466 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
467 }
468 }
469
470 public InputMethodSettings(
471 Resources res, ContentResolver resolver,
472 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
473 int userId) {
474 setCurrentUserId(userId);
475 mRes = res;
476 mResolver = resolver;
477 mMethodMap = methodMap;
478 mMethodList = methodList;
479 }
480
481 public void setCurrentUserId(int userId) {
482 if (DEBUG) {
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900483 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900484 }
485 // IMMS settings are kept per user, so keep track of current user
486 mCurrentUserId = userId;
487 }
488
489 public List<InputMethodInfo> getEnabledInputMethodListLocked() {
490 return createEnabledInputMethodListLocked(
491 getEnabledInputMethodsAndSubtypeListLocked());
492 }
493
494 public List<Pair<InputMethodInfo, ArrayList<String>>>
495 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
496 return createEnabledInputMethodAndSubtypeHashCodeListLocked(
497 getEnabledInputMethodsAndSubtypeListLocked());
498 }
499
500 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
501 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
502 List<InputMethodSubtype> enabledSubtypes =
503 getEnabledInputMethodSubtypeListLocked(imi);
504 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
505 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
506 context.getResources(), imi);
507 }
508 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
509 }
510
511 private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
512 InputMethodInfo imi) {
513 List<Pair<String, ArrayList<String>>> imsList =
514 getEnabledInputMethodsAndSubtypeListLocked();
515 ArrayList<InputMethodSubtype> enabledSubtypes =
516 new ArrayList<InputMethodSubtype>();
517 if (imi != null) {
518 for (Pair<String, ArrayList<String>> imsPair : imsList) {
519 InputMethodInfo info = mMethodMap.get(imsPair.first);
520 if (info != null && info.getId().equals(imi.getId())) {
521 final int subtypeCount = info.getSubtypeCount();
522 for (int i = 0; i < subtypeCount; ++i) {
523 InputMethodSubtype ims = info.getSubtypeAt(i);
524 for (String s: imsPair.second) {
525 if (String.valueOf(ims.hashCode()).equals(s)) {
526 enabledSubtypes.add(ims);
527 }
528 }
529 }
530 break;
531 }
532 }
533 }
534 return enabledSubtypes;
535 }
536
537 // At the initial boot, the settings for input methods are not set,
538 // so we need to enable IME in that case.
539 public void enableAllIMEsIfThereIsNoEnabledIME() {
540 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
541 StringBuilder sb = new StringBuilder();
542 final int N = mMethodList.size();
543 for (int i = 0; i < N; i++) {
544 InputMethodInfo imi = mMethodList.get(i);
545 Slog.i(TAG, "Adding: " + imi.getId());
546 if (i > 0) sb.append(':');
547 sb.append(imi.getId());
548 }
549 putEnabledInputMethodsStr(sb.toString());
550 }
551 }
552
553 public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
554 ArrayList<Pair<String, ArrayList<String>>> imsList
555 = new ArrayList<Pair<String, ArrayList<String>>>();
556 final String enabledInputMethodsStr = getEnabledInputMethodsStr();
557 if (TextUtils.isEmpty(enabledInputMethodsStr)) {
558 return imsList;
559 }
560 mInputMethodSplitter.setString(enabledInputMethodsStr);
561 while (mInputMethodSplitter.hasNext()) {
562 String nextImsStr = mInputMethodSplitter.next();
563 mSubtypeSplitter.setString(nextImsStr);
564 if (mSubtypeSplitter.hasNext()) {
565 ArrayList<String> subtypeHashes = new ArrayList<String>();
566 // The first element is ime id.
567 String imeId = mSubtypeSplitter.next();
568 while (mSubtypeSplitter.hasNext()) {
569 subtypeHashes.add(mSubtypeSplitter.next());
570 }
571 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
572 }
573 }
574 return imsList;
575 }
576
577 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
578 if (reloadInputMethodStr) {
579 getEnabledInputMethodsStr();
580 }
581 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
582 // Add in the newly enabled input method.
583 putEnabledInputMethodsStr(id);
584 } else {
585 putEnabledInputMethodsStr(
586 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
587 }
588 }
589
590 /**
591 * Build and put a string of EnabledInputMethods with removing specified Id.
592 * @return the specified id was removed or not.
593 */
594 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
595 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
596 boolean isRemoved = false;
597 boolean needsAppendSeparator = false;
598 for (Pair<String, ArrayList<String>> ims: imsList) {
599 String curId = ims.first;
600 if (curId.equals(id)) {
601 // We are disabling this input method, and it is
602 // currently enabled. Skip it to remove from the
603 // new list.
604 isRemoved = true;
605 } else {
606 if (needsAppendSeparator) {
607 builder.append(INPUT_METHOD_SEPARATER);
608 } else {
609 needsAppendSeparator = true;
610 }
611 buildEnabledInputMethodsSettingString(builder, ims);
612 }
613 }
614 if (isRemoved) {
615 // Update the setting with the new list of input methods.
616 putEnabledInputMethodsStr(builder.toString());
617 }
618 return isRemoved;
619 }
620
621 private List<InputMethodInfo> createEnabledInputMethodListLocked(
622 List<Pair<String, ArrayList<String>>> imsList) {
623 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
624 for (Pair<String, ArrayList<String>> ims: imsList) {
625 InputMethodInfo info = mMethodMap.get(ims.first);
626 if (info != null) {
627 res.add(info);
628 }
629 }
630 return res;
631 }
632
633 private List<Pair<InputMethodInfo, ArrayList<String>>>
634 createEnabledInputMethodAndSubtypeHashCodeListLocked(
635 List<Pair<String, ArrayList<String>>> imsList) {
636 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
637 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
638 for (Pair<String, ArrayList<String>> ims : imsList) {
639 InputMethodInfo info = mMethodMap.get(ims.first);
640 if (info != null) {
641 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
642 }
643 }
644 return res;
645 }
646
647 private void putEnabledInputMethodsStr(String str) {
648 Settings.Secure.putStringForUser(
649 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
650 mEnabledInputMethodsStrCache = str;
651 if (DEBUG) {
652 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
653 }
654 }
655
Dianne Hackbornfd7aded2013-01-22 17:10:23 -0800656 public String getEnabledInputMethodsStr() {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900657 mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
658 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
659 if (DEBUG) {
660 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
661 + ", " + mCurrentUserId);
662 }
663 return mEnabledInputMethodsStrCache;
664 }
665
666 private void saveSubtypeHistory(
667 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
668 StringBuilder builder = new StringBuilder();
669 boolean isImeAdded = false;
670 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
671 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
672 newSubtypeId);
673 isImeAdded = true;
674 }
675 for (Pair<String, String> ime: savedImes) {
676 String imeId = ime.first;
677 String subtypeId = ime.second;
678 if (TextUtils.isEmpty(subtypeId)) {
679 subtypeId = NOT_A_SUBTYPE_ID_STR;
680 }
681 if (isImeAdded) {
682 builder.append(INPUT_METHOD_SEPARATER);
683 } else {
684 isImeAdded = true;
685 }
686 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
687 subtypeId);
688 }
689 // Remove the last INPUT_METHOD_SEPARATER
690 putSubtypeHistoryStr(builder.toString());
691 }
692
693 private void addSubtypeToHistory(String imeId, String subtypeId) {
694 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
695 for (Pair<String, String> ime: subtypeHistory) {
696 if (ime.first.equals(imeId)) {
697 if (DEBUG) {
698 Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
699 + ime.second);
700 }
701 // We should break here
702 subtypeHistory.remove(ime);
703 break;
704 }
705 }
706 if (DEBUG) {
707 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
708 }
709 saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
710 }
711
712 private void putSubtypeHistoryStr(String str) {
713 if (DEBUG) {
714 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
715 }
716 Settings.Secure.putStringForUser(
717 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
718 }
719
720 public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
721 // Gets the first one from the history
722 return getLastSubtypeForInputMethodLockedInternal(null);
723 }
724
725 public String getLastSubtypeForInputMethodLocked(String imeId) {
726 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
727 if (ime != null) {
728 return ime.second;
729 } else {
730 return null;
731 }
732 }
733
734 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
735 List<Pair<String, ArrayList<String>>> enabledImes =
736 getEnabledInputMethodsAndSubtypeListLocked();
737 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
738 for (Pair<String, String> imeAndSubtype : subtypeHistory) {
739 final String imeInTheHistory = imeAndSubtype.first;
740 // If imeId is empty, returns the first IME and subtype in the history
741 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
742 final String subtypeInTheHistory = imeAndSubtype.second;
743 final String subtypeHashCode =
744 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
745 enabledImes, imeInTheHistory, subtypeInTheHistory);
746 if (!TextUtils.isEmpty(subtypeHashCode)) {
747 if (DEBUG) {
748 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
749 }
750 return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
751 }
752 }
753 }
754 if (DEBUG) {
755 Slog.d(TAG, "No enabled IME found in the history");
756 }
757 return null;
758 }
759
760 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
761 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
762 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
763 if (enabledIme.first.equals(imeId)) {
764 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
765 final InputMethodInfo imi = mMethodMap.get(imeId);
766 if (explicitlyEnabledSubtypes.size() == 0) {
767 // If there are no explicitly enabled subtypes, applicable subtypes are
768 // enabled implicitly.
769 // If IME is enabled and no subtypes are enabled, applicable subtypes
770 // are enabled implicitly, so needs to treat them to be enabled.
771 if (imi != null && imi.getSubtypeCount() > 0) {
772 List<InputMethodSubtype> implicitlySelectedSubtypes =
773 getImplicitlyApplicableSubtypesLocked(mRes, imi);
774 if (implicitlySelectedSubtypes != null) {
775 final int N = implicitlySelectedSubtypes.size();
776 for (int i = 0; i < N; ++i) {
777 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
778 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
779 return subtypeHashCode;
780 }
781 }
782 }
783 }
784 } else {
785 for (String s: explicitlyEnabledSubtypes) {
786 if (s.equals(subtypeHashCode)) {
787 // If both imeId and subtypeId are enabled, return subtypeId.
788 try {
789 final int hashCode = Integer.valueOf(subtypeHashCode);
790 // Check whether the subtype id is valid or not
791 if (isValidSubtypeId(imi, hashCode)) {
792 return s;
793 } else {
794 return NOT_A_SUBTYPE_ID_STR;
795 }
796 } catch (NumberFormatException e) {
797 return NOT_A_SUBTYPE_ID_STR;
798 }
799 }
800 }
801 }
802 // If imeId was enabled but subtypeId was disabled.
803 return NOT_A_SUBTYPE_ID_STR;
804 }
805 }
806 // If both imeId and subtypeId are disabled, return null
807 return null;
808 }
809
810 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
811 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
812 final String subtypeHistoryStr = getSubtypeHistoryStr();
813 if (TextUtils.isEmpty(subtypeHistoryStr)) {
814 return imsList;
815 }
816 mInputMethodSplitter.setString(subtypeHistoryStr);
817 while (mInputMethodSplitter.hasNext()) {
818 String nextImsStr = mInputMethodSplitter.next();
819 mSubtypeSplitter.setString(nextImsStr);
820 if (mSubtypeSplitter.hasNext()) {
821 String subtypeId = NOT_A_SUBTYPE_ID_STR;
822 // The first element is ime id.
823 String imeId = mSubtypeSplitter.next();
824 while (mSubtypeSplitter.hasNext()) {
825 subtypeId = mSubtypeSplitter.next();
826 break;
827 }
828 imsList.add(new Pair<String, String>(imeId, subtypeId));
829 }
830 }
831 return imsList;
832 }
833
834 private String getSubtypeHistoryStr() {
835 if (DEBUG) {
836 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
837 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
838 }
839 return Settings.Secure.getStringForUser(
840 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
841 }
842
843 public void putSelectedInputMethod(String imeId) {
844 if (DEBUG) {
845 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
846 + mCurrentUserId);
847 }
848 Settings.Secure.putStringForUser(
849 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
850 }
851
852 public void putSelectedSubtype(int subtypeId) {
853 if (DEBUG) {
854 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
855 + mCurrentUserId);
856 }
857 Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
858 subtypeId, mCurrentUserId);
859 }
860
861 public String getDisabledSystemInputMethods() {
862 return Settings.Secure.getStringForUser(
863 mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
864 }
865
866 public String getSelectedInputMethod() {
867 if (DEBUG) {
868 Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
869 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
870 + ", " + mCurrentUserId);
871 }
872 return Settings.Secure.getStringForUser(
873 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
874 }
875
876 public boolean isSubtypeSelected() {
877 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
878 }
879
880 private int getSelectedInputMethodSubtypeHashCode() {
881 try {
882 return Settings.Secure.getIntForUser(
883 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
884 } catch (SettingNotFoundException e) {
885 return NOT_A_SUBTYPE_ID;
886 }
887 }
888
889 public int getCurrentUserId() {
890 return mCurrentUserId;
891 }
892
893 public int getSelectedInputMethodSubtypeId(String selectedImiId) {
894 final InputMethodInfo imi = mMethodMap.get(selectedImiId);
895 if (imi == null) {
896 return NOT_A_SUBTYPE_ID;
897 }
898 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
899 return getSubtypeIdFromHashCode(imi, subtypeHashCode);
900 }
901
902 public void saveCurrentInputMethodAndSubtypeToHistory(
903 String curMethodId, InputMethodSubtype currentSubtype) {
904 String subtypeId = NOT_A_SUBTYPE_ID_STR;
905 if (currentSubtype != null) {
906 subtypeId = String.valueOf(currentSubtype.hashCode());
907 }
908 if (canAddToLastInputMethod(currentSubtype)) {
909 addSubtypeToHistory(curMethodId, subtypeId);
910 }
911 }
912 }
913}