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