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