blob: 4349b4aa360325cbc2c90f4be4d2fe47727d8ad1 [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
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070017package com.android.server.inputmethod;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090018
Yohei Yukawab21220e2014-11-01 21:04:30 +090019import android.annotation.NonNull;
20import android.annotation.Nullable;
Yohei Yukawa68645a62016-02-17 07:54:20 -080021import android.annotation.UserIdInt;
Yohei Yukawae63b5fa2014-09-19 13:14:55 +090022import android.app.AppOpsManager;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090023import android.content.ContentResolver;
24import android.content.Context;
25import android.content.pm.ApplicationInfo;
Yohei Yukawa094c71f2015-06-20 00:41:31 -070026import android.content.pm.IPackageManager;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090027import android.content.pm.PackageManager;
28import android.content.res.Resources;
Yohei Yukawacf68d522017-12-12 09:33:26 -080029import android.os.Build;
Yohei Yukawa23cbe852016-05-17 16:42:58 -070030import android.os.LocaleList;
Yohei Yukawa094c71f2015-06-20 00:41:31 -070031import android.os.RemoteException;
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -080032import android.os.UserHandle;
33import android.os.UserManagerInternal;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090034import android.provider.Settings;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090035import android.text.TextUtils;
Yohei Yukawa1dd7de62018-11-20 19:25:29 -080036import android.util.ArrayMap;
Yohei Yukawa15be5e62019-01-25 17:27:03 -080037import android.util.ArraySet;
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -080038import android.util.IntArray;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090039import android.util.Pair;
Yohei Yukawa68645a62016-02-17 07:54:20 -080040import android.util.Printer;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090041import android.util.Slog;
42import android.view.inputmethod.InputMethodInfo;
43import android.view.inputmethod.InputMethodSubtype;
Yohei Yukawaa878b952019-01-10 19:36:24 -080044import android.view.inputmethod.InputMethodSystemProperty;
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +090045import android.view.textservice.SpellCheckerInfo;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090046
Yohei Yukawaccb024a2016-06-13 22:16:52 -070047import com.android.internal.annotations.GuardedBy;
Yohei Yukawae72d1c82015-02-20 20:55:21 +090048import com.android.internal.annotations.VisibleForTesting;
Yohei Yukawa35fa6d52018-10-31 11:33:32 -070049import com.android.internal.inputmethod.StartInputFlags;
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -080050import com.android.server.LocalServices;
Yohei Yukawaafec9a242018-12-20 20:53:12 -080051import com.android.server.textservices.TextServicesManagerInternal;
Yohei Yukawae72d1c82015-02-20 20:55:21 +090052
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -080053import java.io.PrintWriter;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090054import java.util.ArrayList;
Yohei Yukawab21220e2014-11-01 21:04:30 +090055import java.util.Arrays;
Yohei Yukawab21220e2014-11-01 21:04:30 +090056import java.util.LinkedHashSet;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090057import java.util.List;
58import java.util.Locale;
59
60/**
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070061 * This class provides random static utility methods for {@link InputMethodManagerService} and its
62 * utility classes.
63 *
64 * <p>This class is intentionally package-private. Utility methods here are tightly coupled with
65 * implementation details in {@link InputMethodManagerService}. Hence this class is not suitable
66 * for other components to directly use.</p>
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090067 */
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070068final class InputMethodUtils {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090069 public static final boolean DEBUG = false;
Yohei Yukawa1c663012019-01-25 02:44:00 -080070 static final int NOT_A_SUBTYPE_ID = -1;
71 private static final String SUBTYPE_MODE_ANY = null;
72 static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090073 private static final String TAG = "InputMethodUtils";
74 private static final Locale ENGLISH_LOCALE = new Locale("en");
75 private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
76 private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
77 "EnabledWhenDefaultIsNotAsciiCapable";
Seigo Nonakace2c7842015-08-17 08:47:36 -070078
79 // The string for enabled input method is saved as follows:
80 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
Seigo Nonaka2028dda2015-07-06 17:41:24 +090081 private static final char INPUT_METHOD_SEPARATOR = ':';
82 private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
Yohei Yukawadc489242014-09-14 12:01:59 +090083 /**
84 * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
85 * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
86 * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
87 * doesn't automatically match {@code Locale("en", "IN")}.
88 */
89 private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
90 Locale.ENGLISH, // "en"
91 Locale.US, // "en_US"
92 Locale.UK, // "en_GB"
93 };
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +090094
Yohei Yukawaccb024a2016-06-13 22:16:52 -070095 // A temporary workaround for the performance concerns in
96 // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
97 // TODO: Optimize all the critical paths including this one.
98 private static final Object sCacheLock = new Object();
99 @GuardedBy("sCacheLock")
100 private static LocaleList sCachedSystemLocales;
101 @GuardedBy("sCacheLock")
102 private static InputMethodInfo sCachedInputMethodInfo;
103 @GuardedBy("sCacheLock")
104 private static ArrayList<InputMethodSubtype> sCachedResult;
105
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900106 private InputMethodUtils() {
107 // This utility class is not publicly instantiable.
108 }
109
Satoshi Kataoka0766eb02013-07-31 18:30:13 +0900110 // ----------------------------------------------------------------------
111 // Utilities for debug
Yohei Yukawa1c663012019-01-25 02:44:00 -0800112 static String getApiCallStack() {
Satoshi Kataoka87c29142013-07-31 23:11:54 +0900113 String apiCallStack = "";
114 try {
115 throw new RuntimeException();
116 } catch (RuntimeException e) {
117 final StackTraceElement[] frames = e.getStackTrace();
118 for (int j = 1; j < frames.length; ++j) {
119 final String tempCallStack = frames[j].toString();
120 if (TextUtils.isEmpty(apiCallStack)) {
121 // Overwrite apiCallStack if it's empty
122 apiCallStack = tempCallStack;
123 } else if (tempCallStack.indexOf("Transact(") < 0) {
124 // Overwrite apiCallStack if it's not a binder call
125 apiCallStack = tempCallStack;
126 } else {
127 break;
128 }
129 }
130 }
131 return apiCallStack;
132 }
Satoshi Kataoka0766eb02013-07-31 18:30:13 +0900133 // ----------------------------------------------------------------------
134
Yohei Yukawa1c663012019-01-25 02:44:00 -0800135 private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800136 boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
137 String requiredSubtypeMode) {
Yohei Yukawafd70fe82018-04-08 12:19:56 -0700138 if (!imi.isSystem()) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900139 return false;
140 }
141 if (checkDefaultAttribute && !imi.isDefault(context)) {
142 return false;
143 }
144 if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) {
145 return false;
146 }
147 return true;
148 }
149
150 @Nullable
Yohei Yukawa1c663012019-01-25 02:44:00 -0800151 private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800152 Context context) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900153 // At first, find the fallback locale from the IMEs that are declared as "default" in the
154 // current locale. Note that IME developers can declare an IME as "default" only for
155 // some particular locales but "not default" for other locales.
Yohei Yukawadc489242014-09-14 12:01:59 +0900156 for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
157 for (int i = 0; i < imis.size(); ++i) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900158 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
159 true /* checkDefaultAttribute */, fallbackLocale,
160 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
Yohei Yukawadc489242014-09-14 12:01:59 +0900161 return fallbackLocale;
162 }
163 }
164 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900165 // If no fallback locale is found in the above condition, find fallback locales regardless
166 // of the "default" attribute as a last resort.
167 for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
168 for (int i = 0; i < imis.size(); ++i) {
169 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
170 false /* checkDefaultAttribute */, fallbackLocale,
171 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
172 return fallbackLocale;
173 }
174 }
175 }
176 Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
Yohei Yukawadc489242014-09-14 12:01:59 +0900177 return null;
178 }
179
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800180 private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
181 Context context, boolean checkDefaultAttribute) {
Yohei Yukawafd70fe82018-04-08 12:19:56 -0700182 if (!imi.isSystem()) {
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900183 return false;
184 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900185 if (checkDefaultAttribute && !imi.isDefault(context)) {
186 return false;
187 }
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900188 if (!imi.isAuxiliaryIme()) {
189 return false;
190 }
191 final int subtypeCount = imi.getSubtypeCount();
192 for (int i = 0; i < subtypeCount; ++i) {
193 final InputMethodSubtype s = imi.getSubtypeAt(i);
194 if (s.overridesImplicitlyEnabledSubtype()) {
195 return true;
196 }
197 }
198 return false;
199 }
200
Yohei Yukawa1c663012019-01-25 02:44:00 -0800201 private static Locale getSystemLocaleFromContext(Context context) {
Yohei Yukawadc489242014-09-14 12:01:59 +0900202 try {
203 return context.getResources().getConfiguration().locale;
204 } catch (Resources.NotFoundException ex) {
205 return null;
206 }
207 }
208
Yohei Yukawab21220e2014-11-01 21:04:30 +0900209 private static final class InputMethodListBuilder {
210 // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
211 // order can have non-trivial effect in the call sites.
212 @NonNull
213 private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
Yohei Yukawadc489242014-09-14 12:01:59 +0900214
Yohei Yukawa1c663012019-01-25 02:44:00 -0800215 InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800216 boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
217 String requiredSubtypeMode) {
Yohei Yukawa68c860b2014-09-13 22:03:37 +0900218 for (int i = 0; i < imis.size(); ++i) {
219 final InputMethodInfo imi = imis.get(i);
Yohei Yukawab21220e2014-11-01 21:04:30 +0900220 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
221 checkCountry, requiredSubtypeMode)) {
222 mInputMethodSet.add(imi);
Yohei Yukawa68c860b2014-09-13 22:03:37 +0900223 }
224 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900225 return this;
Yohei Yukawa68c860b2014-09-13 22:03:37 +0900226 }
227
Yohei Yukawab21220e2014-11-01 21:04:30 +0900228 // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
229 // documented more clearly.
Yohei Yukawa1c663012019-01-25 02:44:00 -0800230 InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900231 // If one or more auxiliary input methods are available, OK to stop populating the list.
232 for (final InputMethodInfo imi : mInputMethodSet) {
233 if (imi.isAuxiliaryIme()) {
234 return this;
235 }
Yohei Yukawadc489242014-09-14 12:01:59 +0900236 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900237 boolean added = false;
Yohei Yukawadc489242014-09-14 12:01:59 +0900238 for (int i = 0; i < imis.size(); ++i) {
239 final InputMethodInfo imi = imis.get(i);
Yohei Yukawab21220e2014-11-01 21:04:30 +0900240 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
241 true /* checkDefaultAttribute */)) {
242 mInputMethodSet.add(imi);
243 added = true;
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900244 }
245 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900246 if (added) {
247 return this;
248 }
249 for (int i = 0; i < imis.size(); ++i) {
250 final InputMethodInfo imi = imis.get(i);
251 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
252 false /* checkDefaultAttribute */)) {
253 mInputMethodSet.add(imi);
254 }
255 }
256 return this;
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900257 }
Yohei Yukawadc489242014-09-14 12:01:59 +0900258
Yohei Yukawab21220e2014-11-01 21:04:30 +0900259 public boolean isEmpty() {
260 return mInputMethodSet.isEmpty();
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900261 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900262
263 @NonNull
264 public ArrayList<InputMethodInfo> build() {
265 return new ArrayList<>(mInputMethodSet);
Satoshi Kataokaf1367b72013-01-25 17:20:12 +0900266 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900267 }
268
Yohei Yukawab21220e2014-11-01 21:04:30 +0900269 private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800270 ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
271 @Nullable Locale fallbackLocale) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900272 // Once the system becomes ready, we pick up at least one keyboard in the following order.
273 // Secondary users fall into this category in general.
274 // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
275 // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
276 // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
277 // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
278 // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
279 // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
280 // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
281
282 final InputMethodListBuilder builder = new InputMethodListBuilder();
283 builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
284 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
285 if (!builder.isEmpty()) {
286 return builder;
287 }
288 builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
289 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
290 if (!builder.isEmpty()) {
291 return builder;
292 }
293 builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
294 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
295 if (!builder.isEmpty()) {
296 return builder;
297 }
298 builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
299 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
300 if (!builder.isEmpty()) {
301 return builder;
302 }
303 builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
304 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
305 if (!builder.isEmpty()) {
306 return builder;
307 }
308 builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
309 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
310 if (!builder.isEmpty()) {
311 return builder;
312 }
313 Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
314 + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
315 return builder;
316 }
317
Yohei Yukawa1c663012019-01-25 02:44:00 -0800318 static ArrayList<InputMethodInfo> getDefaultEnabledImes(
Yohei Yukawa9c372192018-03-20 22:54:56 -0700319 Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900320 final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
Yohei Yukawaaf5cee82017-01-23 16:17:11 -0800321 // We will primarily rely on the system locale, but also keep relying on the fallback locale
322 // as a last resort.
Yohei Yukawab21220e2014-11-01 21:04:30 +0900323 // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
324 // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
325 // subtype)
326 final Locale systemLocale = getSystemLocaleFromContext(context);
Yohei Yukawa9c372192018-03-20 22:54:56 -0700327 final InputMethodListBuilder builder =
328 getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale);
329 if (!onlyMinimum) {
330 builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
331 true /* checkCountry */, SUBTYPE_MODE_ANY)
332 .fillAuxiliaryImes(imis, context);
333 }
334 return builder.build();
335 }
336
Yohei Yukawa1c663012019-01-25 02:44:00 -0800337 static ArrayList<InputMethodInfo> getDefaultEnabledImes(
Yohei Yukawa9c372192018-03-20 22:54:56 -0700338 Context context, ArrayList<InputMethodInfo> imis) {
339 return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
Yohei Yukawadc489242014-09-14 12:01:59 +0900340 }
341
Yohei Yukawa1c663012019-01-25 02:44:00 -0800342 static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800343 boolean checkCountry, String mode) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900344 if (locale == null) {
345 return false;
346 }
Yohei Yukawadc489242014-09-14 12:01:59 +0900347 final int N = imi.getSubtypeCount();
348 for (int i = 0; i < N; ++i) {
349 final InputMethodSubtype subtype = imi.getSubtypeAt(i);
Yohei Yukawab21220e2014-11-01 21:04:30 +0900350 if (checkCountry) {
Yohei Yukawa92280cd2015-06-02 16:50:14 -0700351 final Locale subtypeLocale = subtype.getLocaleObject();
Yohei Yukawaf487e0e2015-02-21 02:15:48 +0900352 if (subtypeLocale == null ||
353 !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
354 !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
Yohei Yukawadc489242014-09-14 12:01:59 +0900355 continue;
356 }
Yohei Yukawab21220e2014-11-01 21:04:30 +0900357 } else {
358 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
359 subtype.getLocale()));
Yohei Yukawaf487e0e2015-02-21 02:15:48 +0900360 if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
Yohei Yukawab21220e2014-11-01 21:04:30 +0900361 continue;
362 }
Yohei Yukawadc489242014-09-14 12:01:59 +0900363 }
364 if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
365 mode.equalsIgnoreCase(subtype.getMode())) {
366 return true;
367 }
368 }
369 return false;
370 }
371
Yohei Yukawa1c663012019-01-25 02:44:00 -0800372 static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700373 ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900374 final int subtypeCount = imi.getSubtypeCount();
375 for (int i = 0; i < subtypeCount; ++i) {
376 subtypes.add(imi.getSubtypeAt(i));
377 }
378 return subtypes;
379 }
380
Yohei Yukawa1c663012019-01-25 02:44:00 -0800381 static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
Yohei Yukawa5e5c60a2014-09-13 01:13:38 +0900382 if (enabledImes == null || enabledImes.isEmpty()) {
383 return null;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900384 }
Yohei Yukawa5e5c60a2014-09-13 01:13:38 +0900385 // We'd prefer to fall back on a system IME, since that is safer.
386 int i = enabledImes.size();
387 int firstFoundSystemIme = -1;
388 while (i > 0) {
389 i--;
390 final InputMethodInfo imi = enabledImes.get(i);
Yohei Yukawa6aa03782015-02-21 03:00:22 +0900391 if (imi.isAuxiliaryIme()) {
392 continue;
393 }
Yohei Yukawafd70fe82018-04-08 12:19:56 -0700394 if (imi.isSystem() && containsSubtypeOf(
395 imi, ENGLISH_LOCALE, false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
Yohei Yukawa5e5c60a2014-09-13 01:13:38 +0900396 return imi;
397 }
Yohei Yukawafd70fe82018-04-08 12:19:56 -0700398 if (firstFoundSystemIme < 0 && imi.isSystem()) {
Yohei Yukawa5e5c60a2014-09-13 01:13:38 +0900399 firstFoundSystemIme = i;
400 }
401 }
402 return enabledImes.get(Math.max(firstFoundSystemIme, 0));
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900403 }
404
Yohei Yukawa1c663012019-01-25 02:44:00 -0800405 static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900406 return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
407 }
408
Yohei Yukawa1c663012019-01-25 02:44:00 -0800409 static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900410 if (imi != null) {
411 final int subtypeCount = imi.getSubtypeCount();
412 for (int i = 0; i < subtypeCount; ++i) {
413 InputMethodSubtype ims = imi.getSubtypeAt(i);
414 if (subtypeHashCode == ims.hashCode()) {
415 return i;
416 }
417 }
418 }
419 return NOT_A_SUBTYPE_ID;
420 }
421
Yohei Yukawae985c242016-02-24 18:27:04 -0800422 private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
423 new LocaleUtils.LocaleExtractor<InputMethodSubtype>() {
424 @Override
425 public Locale get(InputMethodSubtype source) {
426 return source != null ? source.getLocaleObject() : null;
427 }
428 };
429
Yohei Yukawae72d1c82015-02-20 20:55:21 +0900430 @VisibleForTesting
Yohei Yukawa1c663012019-01-25 02:44:00 -0800431 static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900432 Resources res, InputMethodInfo imi) {
Yohei Yukawaccb024a2016-06-13 22:16:52 -0700433 final LocaleList systemLocales = res.getConfiguration().getLocales();
434
435 synchronized (sCacheLock) {
436 // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
437 // it does not check if subtypes are also identical.
438 if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) {
439 return new ArrayList<>(sCachedResult);
440 }
441 }
442
443 // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
444 // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
445 // LocaleList rather than Resource.
446 final ArrayList<InputMethodSubtype> result =
447 getImplicitlyApplicableSubtypesLockedImpl(res, imi);
448 synchronized (sCacheLock) {
449 // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
450 sCachedSystemLocales = systemLocales;
451 sCachedInputMethodInfo = imi;
452 sCachedResult = new ArrayList<>(result);
453 }
454 return result;
455 }
456
457 private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
458 Resources res, InputMethodInfo imi) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900459 final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
Yohei Yukawae985c242016-02-24 18:27:04 -0800460 final LocaleList systemLocales = res.getConfiguration().getLocales();
461 final String systemLocale = systemLocales.get(0).toString();
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700462 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
Yohei Yukawae985c242016-02-24 18:27:04 -0800463 final int numSubtypes = subtypes.size();
464
465 // Handle overridesImplicitlyEnabledSubtype mechanism.
Yohei Yukawa1dd7de62018-11-20 19:25:29 -0800466 final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>();
Yohei Yukawae985c242016-02-24 18:27:04 -0800467 for (int i = 0; i < numSubtypes; ++i) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900468 // scan overriding implicitly enabled subtypes.
Yohei Yukawae985c242016-02-24 18:27:04 -0800469 final InputMethodSubtype subtype = subtypes.get(i);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900470 if (subtype.overridesImplicitlyEnabledSubtype()) {
471 final String mode = subtype.getMode();
472 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
473 applicableModeAndSubtypesMap.put(mode, subtype);
474 }
475 }
476 }
477 if (applicableModeAndSubtypesMap.size() > 0) {
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700478 return new ArrayList<>(applicableModeAndSubtypesMap.values());
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900479 }
Yohei Yukawae985c242016-02-24 18:27:04 -0800480
Yohei Yukawa1dd7de62018-11-20 19:25:29 -0800481 final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap =
482 new ArrayMap<>();
Yohei Yukawae985c242016-02-24 18:27:04 -0800483 final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>();
Yohei Yukawa238faad2016-03-11 10:39:51 -0800484
Yohei Yukawae985c242016-02-24 18:27:04 -0800485 for (int i = 0; i < numSubtypes; ++i) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900486 final InputMethodSubtype subtype = subtypes.get(i);
Yohei Yukawa238faad2016-03-11 10:39:51 -0800487 final String mode = subtype.getMode();
488 if (SUBTYPE_MODE_KEYBOARD.equals(mode)) {
Yohei Yukawae985c242016-02-24 18:27:04 -0800489 keyboardSubtypes.add(subtype);
490 } else {
Yohei Yukawa238faad2016-03-11 10:39:51 -0800491 if (!nonKeyboardSubtypesMap.containsKey(mode)) {
492 nonKeyboardSubtypesMap.put(mode, new ArrayList<>());
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900493 }
Yohei Yukawa238faad2016-03-11 10:39:51 -0800494 nonKeyboardSubtypesMap.get(mode).add(subtype);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900495 }
496 }
Yohei Yukawae985c242016-02-24 18:27:04 -0800497
498 final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>();
499 LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales,
500 applicableSubtypes);
501
Yohei Yukawa42275bc2016-03-03 00:34:27 -0800502 if (!applicableSubtypes.isEmpty()) {
503 boolean hasAsciiCapableKeyboard = false;
504 final int numApplicationSubtypes = applicableSubtypes.size();
505 for (int i = 0; i < numApplicationSubtypes; ++i) {
506 final InputMethodSubtype subtype = applicableSubtypes.get(i);
Yohei Yukawab1845f32018-04-24 18:36:23 -0700507 if (subtype.isAsciiCapable()) {
Yohei Yukawa42275bc2016-03-03 00:34:27 -0800508 hasAsciiCapableKeyboard = true;
509 break;
510 }
Yohei Yukawae985c242016-02-24 18:27:04 -0800511 }
Yohei Yukawa42275bc2016-03-03 00:34:27 -0800512 if (!hasAsciiCapableKeyboard) {
513 final int numKeyboardSubtypes = keyboardSubtypes.size();
514 for (int i = 0; i < numKeyboardSubtypes; ++i) {
515 final InputMethodSubtype subtype = keyboardSubtypes.get(i);
516 final String mode = subtype.getMode();
517 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
518 TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
519 applicableSubtypes.add(subtype);
520 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900521 }
522 }
523 }
Yohei Yukawae985c242016-02-24 18:27:04 -0800524
525 if (applicableSubtypes.isEmpty()) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900526 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
527 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
528 if (lastResortKeyboardSubtype != null) {
529 applicableSubtypes.add(lastResortKeyboardSubtype);
530 }
531 }
Yohei Yukawae985c242016-02-24 18:27:04 -0800532
Yohei Yukawa238faad2016-03-11 10:39:51 -0800533 // For each non-keyboard mode, extract subtypes with system locales.
534 for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) {
535 LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales,
536 applicableSubtypes);
537 }
538
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900539 return applicableSubtypes;
540 }
541
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900542 /**
Narayan Kamath4d8c1322014-07-11 11:50:24 +0100543 * Returns the language component of a given locale string.
Yohei Yukawab21220e2014-11-01 21:04:30 +0900544 * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
Narayan Kamath4d8c1322014-07-11 11:50:24 +0100545 */
Yohei Yukawae9eff662018-04-13 16:28:20 -0700546 private static String getLanguageFromLocaleString(String locale) {
Narayan Kamath4d8c1322014-07-11 11:50:24 +0100547 final int idx = locale.indexOf('_');
548 if (idx < 0) {
549 return locale;
550 } else {
551 return locale.substring(0, idx);
552 }
553 }
554
555 /**
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900556 * If there are no selected subtypes, tries finding the most applicable one according to the
557 * given locale.
558 * @param subtypes this function will search the most applicable subtype in subtypes
559 * @param mode subtypes will be filtered by mode
560 * @param locale subtypes will be filtered by locale
561 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
562 * it will return the first subtype matched with mode
563 * @return the most applicable subtypeId
564 */
Yohei Yukawa1c663012019-01-25 02:44:00 -0800565 static InputMethodSubtype findLastResortApplicableSubtypeLocked(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900566 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
567 boolean canIgnoreLocaleAsLastResort) {
568 if (subtypes == null || subtypes.size() == 0) {
569 return null;
570 }
571 if (TextUtils.isEmpty(locale)) {
572 locale = res.getConfiguration().locale.toString();
573 }
Narayan Kamath4d8c1322014-07-11 11:50:24 +0100574 final String language = getLanguageFromLocaleString(locale);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900575 boolean partialMatchFound = false;
576 InputMethodSubtype applicableSubtype = null;
577 InputMethodSubtype firstMatchedModeSubtype = null;
578 final int N = subtypes.size();
579 for (int i = 0; i < N; ++i) {
580 InputMethodSubtype subtype = subtypes.get(i);
581 final String subtypeLocale = subtype.getLocale();
Narayan Kamath4d8c1322014-07-11 11:50:24 +0100582 final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900583 // An applicable subtype should match "mode". If mode is null, mode will be ignored,
584 // and all subtypes with all modes can be candidates.
585 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
586 if (firstMatchedModeSubtype == null) {
587 firstMatchedModeSubtype = subtype;
588 }
589 if (locale.equals(subtypeLocale)) {
590 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
591 applicableSubtype = subtype;
592 break;
Narayan Kamath4d8c1322014-07-11 11:50:24 +0100593 } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900594 // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
595 applicableSubtype = subtype;
596 partialMatchFound = true;
597 }
598 }
599 }
600
601 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
602 return firstMatchedModeSubtype;
603 }
604
605 // The first subtype applicable to the system locale will be defined as the most applicable
606 // subtype.
607 if (DEBUG) {
608 if (applicableSubtype != null) {
609 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
610 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
611 }
612 }
613 return applicableSubtype;
614 }
615
Yohei Yukawa1c663012019-01-25 02:44:00 -0800616 static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900617 if (subtype == null) return true;
618 return !subtype.isAuxiliary();
619 }
620
Yohei Yukawa1c663012019-01-25 02:44:00 -0800621 static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager,
622 List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) {
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900623 if (DEBUG) {
624 Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
625 }
626 final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
627 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
628 if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
629 return;
630 }
631 // Only the current spell checker should be treated as an enabled one.
632 final SpellCheckerInfo currentSpellChecker =
Yohei Yukawaafec9a242018-12-20 20:53:12 -0800633 TextServicesManagerInternal.get().getCurrentSpellCheckerForUser(userId);
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900634 for (final String packageName : systemImesDisabledUntilUsed) {
635 if (DEBUG) {
636 Slog.d(TAG, "check " + packageName);
637 }
638 boolean enabledIme = false;
639 for (int j = 0; j < enabledImis.size(); ++j) {
640 final InputMethodInfo imi = enabledImis.get(j);
641 if (packageName.equals(imi.getPackageName())) {
642 enabledIme = true;
643 break;
644 }
645 }
646 if (enabledIme) {
647 // enabled ime. skip
648 continue;
649 }
650 if (currentSpellChecker != null
651 && packageName.equals(currentSpellChecker.getPackageName())) {
652 // enabled spell checker. skip
653 if (DEBUG) {
654 Slog.d(TAG, packageName + " is the current spell checker. skip");
655 }
656 continue;
657 }
658 ApplicationInfo ai = null;
659 try {
660 ai = packageManager.getApplicationInfo(packageName,
Yohei Yukawa094c71f2015-06-20 00:41:31 -0700661 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
662 } catch (RemoteException e) {
663 Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName
664 + " userId=" + userId, e);
665 continue;
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900666 }
667 if (ai == null) {
668 // No app found for packageName
669 continue;
670 }
671 final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
672 if (!isSystemPackage) {
673 continue;
674 }
Yohei Yukawa094c71f2015-06-20 00:41:31 -0700675 setDisabledUntilUsed(packageManager, packageName, userId, callingPackage);
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900676 }
677 }
678
Yohei Yukawa094c71f2015-06-20 00:41:31 -0700679 private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName,
680 int userId, String callingPackage) {
681 final int state;
682 try {
683 state = packageManager.getApplicationEnabledSetting(packageName, userId);
684 } catch (RemoteException e) {
685 Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
686 + " userId=" + userId, e);
687 return;
688 }
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900689 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
690 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
691 if (DEBUG) {
692 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
693 }
Yohei Yukawa094c71f2015-06-20 00:41:31 -0700694 try {
695 packageManager.setApplicationEnabledSetting(packageName,
696 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
697 0 /* newState */, userId, callingPackage);
698 } catch (RemoteException e) {
699 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
700 + " userId=" + userId + " callingPackage=" + callingPackage, e);
701 return;
702 }
Satoshi Kataokaed1cdb22013-04-17 16:41:58 +0900703 } else {
704 if (DEBUG) {
705 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
706 }
707 }
708 }
709
Yohei Yukawa1c663012019-01-25 02:44:00 -0800710 static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
Satoshi Kataokab2827262013-07-04 19:43:14 +0900711 InputMethodSubtype subtype) {
712 final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
713 return subtype != null
714 ? TextUtils.concat(subtype.getDisplayName(context,
715 imi.getPackageName(), imi.getServiceInfo().applicationInfo),
716 (TextUtils.isEmpty(imiLabel) ?
717 "" : " - " + imiLabel))
718 : imiLabel;
719 }
720
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900721 /**
Yohei Yukawae63b5fa2014-09-19 13:14:55 +0900722 * Returns true if a package name belongs to a UID.
723 *
724 * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
725 * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
726 * @param uid the UID to be validated.
727 * @param packageName the package name.
728 * @return {@code true} if the package name belongs to the UID.
729 */
Yohei Yukawa1c663012019-01-25 02:44:00 -0800730 static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager,
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800731 @UserIdInt int uid, String packageName) {
Yohei Yukawae63b5fa2014-09-19 13:14:55 +0900732 try {
733 appOpsManager.checkPackage(uid, packageName);
734 return true;
735 } catch (SecurityException e) {
736 return false;
737 }
738 }
739
740 /**
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900741 * Utility class for putting and getting settings for InputMethod
742 * TODO: Move all putters and getters of settings to this class.
743 */
744 public static class InputMethodSettings {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900745 private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
Seigo Nonakace2c7842015-08-17 08:47:36 -0700746 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900747
748 private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
Seigo Nonakace2c7842015-08-17 08:47:36 -0700749 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900750
751 private final Resources mRes;
752 private final ContentResolver mResolver;
Yohei Yukawa1dd7de62018-11-20 19:25:29 -0800753 private final ArrayMap<String, InputMethodInfo> mMethodMap;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900754
Yohei Yukawa68645a62016-02-17 07:54:20 -0800755 /**
756 * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
757 */
Yohei Yukawa1dd7de62018-11-20 19:25:29 -0800758 private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
Yohei Yukawa68645a62016-02-17 07:54:20 -0800759
Yohei Yukawa15be5e62019-01-25 17:27:03 -0800760 private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
761 static {
762 Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
763 }
764
765 private static final UserManagerInternal sUserManagerInternal =
766 LocalServices.getService(UserManagerInternal.class);
767
Yohei Yukawa68645a62016-02-17 07:54:20 -0800768 private boolean mCopyOnWrite = false;
Yohei Yukawa7b574cb2016-03-16 17:22:22 -0700769 @NonNull
770 private String mEnabledInputMethodsStrCache = "";
Yohei Yukawa68645a62016-02-17 07:54:20 -0800771 @UserIdInt
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900772 private int mCurrentUserId;
Kenny Guy2a764942014-04-02 13:29:20 +0100773 private int[] mCurrentProfileIds = new int[0];
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900774
775 private static void buildEnabledInputMethodsSettingString(
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700776 StringBuilder builder, Pair<String, ArrayList<String>> ime) {
777 builder.append(ime.first);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900778 // Inputmethod and subtypes are saved in the settings as follows:
779 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700780 for (String subtypeId: ime.second) {
Seigo Nonakace2c7842015-08-17 08:47:36 -0700781 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900782 }
783 }
784
Yohei Yukawa08a3ece2018-04-10 14:50:53 -0700785 private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700786 String enabledInputMethodsStr,
787 TextUtils.SimpleStringSplitter inputMethodSplitter,
788 TextUtils.SimpleStringSplitter subtypeSplitter) {
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700789 ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700790 if (TextUtils.isEmpty(enabledInputMethodsStr)) {
791 return imsList;
792 }
793 inputMethodSplitter.setString(enabledInputMethodsStr);
794 while (inputMethodSplitter.hasNext()) {
795 String nextImsStr = inputMethodSplitter.next();
796 subtypeSplitter.setString(nextImsStr);
797 if (subtypeSplitter.hasNext()) {
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700798 ArrayList<String> subtypeHashes = new ArrayList<>();
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700799 // The first element is ime id.
800 String imeId = subtypeSplitter.next();
801 while (subtypeSplitter.hasNext()) {
802 subtypeHashes.add(subtypeSplitter.next());
803 }
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700804 imsList.add(new Pair<>(imeId, subtypeHashes));
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700805 }
806 }
807 return imsList;
808 }
809
Yohei Yukawa1c663012019-01-25 02:44:00 -0800810 InputMethodSettings(Resources res, ContentResolver resolver,
Yohei Yukawaf9277532019-01-25 02:47:32 -0800811 ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
812 boolean copyOnWrite) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900813 mRes = res;
814 mResolver = resolver;
815 mMethodMap = methodMap;
Yohei Yukawa68645a62016-02-17 07:54:20 -0800816 switchCurrentUser(userId, copyOnWrite);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900817 }
818
Yohei Yukawa68645a62016-02-17 07:54:20 -0800819 /**
820 * Must be called when the current user is changed.
821 *
822 * @param userId The user ID.
823 * @param copyOnWrite If {@code true}, for each settings key
824 * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
825 * settings on the {@link Settings.Secure} until we do the first write operation.
826 */
Yohei Yukawa1c663012019-01-25 02:44:00 -0800827 void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900828 if (DEBUG) {
Yohei Yukawa68645a62016-02-17 07:54:20 -0800829 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900830 }
Yohei Yukawa68645a62016-02-17 07:54:20 -0800831 if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
832 mCopyOnWriteDataStore.clear();
833 mEnabledInputMethodsStrCache = "";
834 // TODO: mCurrentProfileIds should be cleared here.
835 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900836 mCurrentUserId = userId;
Yohei Yukawa68645a62016-02-17 07:54:20 -0800837 mCopyOnWrite = copyOnWrite;
838 // TODO: mCurrentProfileIds should be updated here.
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900839 }
840
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800841 private void putString(@NonNull String key, @Nullable String str) {
Yohei Yukawa68645a62016-02-17 07:54:20 -0800842 if (mCopyOnWrite) {
843 mCopyOnWriteDataStore.put(key, str);
844 } else {
Yohei Yukawa15be5e62019-01-25 17:27:03 -0800845 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
846 ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
847 Settings.Secure.putStringForUser(mResolver, key, str, userId);
Yohei Yukawa68645a62016-02-17 07:54:20 -0800848 }
Yohei Yukawa87523672016-02-12 19:37:08 -0800849 }
850
Yohei Yukawa7b574cb2016-03-16 17:22:22 -0700851 @Nullable
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800852 private String getString(@NonNull String key, @Nullable String defaultValue) {
Yohei Yukawa7b574cb2016-03-16 17:22:22 -0700853 final String result;
Yohei Yukawa68645a62016-02-17 07:54:20 -0800854 if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
Yohei Yukawa7b574cb2016-03-16 17:22:22 -0700855 result = mCopyOnWriteDataStore.get(key);
856 } else {
857 result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
Yohei Yukawa68645a62016-02-17 07:54:20 -0800858 }
Yohei Yukawa7b574cb2016-03-16 17:22:22 -0700859 return result != null ? result : defaultValue;
Yohei Yukawa87523672016-02-12 19:37:08 -0800860 }
861
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800862 private void putInt(String key, int value) {
Yohei Yukawa68645a62016-02-17 07:54:20 -0800863 if (mCopyOnWrite) {
864 mCopyOnWriteDataStore.put(key, String.valueOf(value));
865 } else {
Yohei Yukawa15be5e62019-01-25 17:27:03 -0800866 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
867 ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
868 Settings.Secure.putIntForUser(mResolver, key, value, userId);
Yohei Yukawa68645a62016-02-17 07:54:20 -0800869 }
Yohei Yukawa87523672016-02-12 19:37:08 -0800870 }
871
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800872 private int getInt(String key, int defaultValue) {
Yohei Yukawa68645a62016-02-17 07:54:20 -0800873 if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
874 final String result = mCopyOnWriteDataStore.get(key);
Yohei Yukawa6ab2e4a2019-01-27 23:18:32 -0800875 return result != null ? Integer.parseInt(result) : defaultValue;
Yohei Yukawa68645a62016-02-17 07:54:20 -0800876 }
Yohei Yukawa87523672016-02-12 19:37:08 -0800877 return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
878 }
879
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800880 private void putBoolean(String key, boolean value) {
Yohei Yukawa68645a62016-02-17 07:54:20 -0800881 putInt(key, value ? 1 : 0);
Yohei Yukawa87523672016-02-12 19:37:08 -0800882 }
883
Yohei Yukawa4792bc12019-01-25 02:26:54 -0800884 private boolean getBoolean(String key, boolean defaultValue) {
Yohei Yukawa68645a62016-02-17 07:54:20 -0800885 return getInt(key, defaultValue ? 1 : 0) == 1;
Yohei Yukawa87523672016-02-12 19:37:08 -0800886 }
887
Kenny Guy2a764942014-04-02 13:29:20 +0100888 public void setCurrentProfileIds(int[] currentProfileIds) {
Amith Yamasani734983f2014-03-04 16:48:05 -0800889 synchronized (this) {
Kenny Guy2a764942014-04-02 13:29:20 +0100890 mCurrentProfileIds = currentProfileIds;
Amith Yamasani734983f2014-03-04 16:48:05 -0800891 }
892 }
893
Kenny Guy2a764942014-04-02 13:29:20 +0100894 public boolean isCurrentProfile(int userId) {
Amith Yamasani734983f2014-03-04 16:48:05 -0800895 synchronized (this) {
Kenny Guyf4824a02014-04-02 19:17:41 +0100896 if (userId == mCurrentUserId) return true;
Kenny Guy2a764942014-04-02 13:29:20 +0100897 for (int i = 0; i < mCurrentProfileIds.length; i++) {
898 if (userId == mCurrentProfileIds[i]) return true;
Amith Yamasani734983f2014-03-04 16:48:05 -0800899 }
900 return false;
901 }
902 }
903
Yohei Yukawa1c663012019-01-25 02:44:00 -0800904 ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900905 return createEnabledInputMethodListLocked(
906 getEnabledInputMethodsAndSubtypeListLocked());
907 }
908
Yohei Yukawa1c663012019-01-25 02:44:00 -0800909 List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900910 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
911 List<InputMethodSubtype> enabledSubtypes =
912 getEnabledInputMethodSubtypeListLocked(imi);
913 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
914 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
915 context.getResources(), imi);
916 }
917 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
918 }
919
Yohei Yukawa1c663012019-01-25 02:44:00 -0800920 List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900921 List<Pair<String, ArrayList<String>>> imsList =
922 getEnabledInputMethodsAndSubtypeListLocked();
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700923 ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900924 if (imi != null) {
925 for (Pair<String, ArrayList<String>> imsPair : imsList) {
926 InputMethodInfo info = mMethodMap.get(imsPair.first);
927 if (info != null && info.getId().equals(imi.getId())) {
928 final int subtypeCount = info.getSubtypeCount();
929 for (int i = 0; i < subtypeCount; ++i) {
930 InputMethodSubtype ims = info.getSubtypeAt(i);
931 for (String s: imsPair.second) {
932 if (String.valueOf(ims.hashCode()).equals(s)) {
933 enabledSubtypes.add(ims);
934 }
935 }
936 }
937 break;
938 }
939 }
940 }
941 return enabledSubtypes;
942 }
943
Yohei Yukawa1c663012019-01-25 02:44:00 -0800944 List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
Christopher Tate7b9a28c2015-03-18 13:06:16 -0700945 return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
946 mInputMethodSplitter,
947 mSubtypeSplitter);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900948 }
949
Yohei Yukawa1c663012019-01-25 02:44:00 -0800950 void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900951 if (reloadInputMethodStr) {
952 getEnabledInputMethodsStr();
953 }
954 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
955 // Add in the newly enabled input method.
956 putEnabledInputMethodsStr(id);
957 } else {
958 putEnabledInputMethodsStr(
Seigo Nonakace2c7842015-08-17 08:47:36 -0700959 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATOR + id);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900960 }
961 }
962
963 /**
964 * Build and put a string of EnabledInputMethods with removing specified Id.
965 * @return the specified id was removed or not.
966 */
Yohei Yukawa1c663012019-01-25 02:44:00 -0800967 boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900968 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
969 boolean isRemoved = false;
970 boolean needsAppendSeparator = false;
971 for (Pair<String, ArrayList<String>> ims: imsList) {
972 String curId = ims.first;
973 if (curId.equals(id)) {
974 // We are disabling this input method, and it is
975 // currently enabled. Skip it to remove from the
976 // new list.
977 isRemoved = true;
978 } else {
979 if (needsAppendSeparator) {
Seigo Nonakace2c7842015-08-17 08:47:36 -0700980 builder.append(INPUT_METHOD_SEPARATOR);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900981 } else {
982 needsAppendSeparator = true;
983 }
984 buildEnabledInputMethodsSettingString(builder, ims);
985 }
986 }
987 if (isRemoved) {
988 // Update the setting with the new list of input methods.
989 putEnabledInputMethodsStr(builder.toString());
990 }
991 return isRemoved;
992 }
993
Yohei Yukawac2393ac2016-02-18 00:30:45 -0800994 private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900995 List<Pair<String, ArrayList<String>>> imsList) {
Yohei Yukawab0377bb2015-08-10 21:06:30 -0700996 final ArrayList<InputMethodInfo> res = new ArrayList<>();
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +0900997 for (Pair<String, ArrayList<String>> ims: imsList) {
998 InputMethodInfo info = mMethodMap.get(ims.first);
Tarandeep Singh89a6c482017-11-21 14:26:11 -0800999 if (info != null && !info.isVrOnly()) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001000 res.add(info);
1001 }
1002 }
1003 return res;
1004 }
1005
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001006 private void putEnabledInputMethodsStr(@Nullable String str) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001007 if (DEBUG) {
1008 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
1009 }
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001010 if (TextUtils.isEmpty(str)) {
1011 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
1012 // empty data scenario.
1013 putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
1014 } else {
1015 putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
1016 }
1017 // TODO: Update callers of putEnabledInputMethodsStr to make str @NonNull.
1018 mEnabledInputMethodsStrCache = (str != null ? str : "");
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001019 }
1020
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001021 @NonNull
Yohei Yukawa1c663012019-01-25 02:44:00 -08001022 String getEnabledInputMethodsStr() {
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001023 mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001024 if (DEBUG) {
1025 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
1026 + ", " + mCurrentUserId);
1027 }
1028 return mEnabledInputMethodsStrCache;
1029 }
1030
1031 private void saveSubtypeHistory(
1032 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
1033 StringBuilder builder = new StringBuilder();
1034 boolean isImeAdded = false;
1035 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
Seigo Nonakace2c7842015-08-17 08:47:36 -07001036 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001037 newSubtypeId);
1038 isImeAdded = true;
1039 }
1040 for (Pair<String, String> ime: savedImes) {
1041 String imeId = ime.first;
1042 String subtypeId = ime.second;
1043 if (TextUtils.isEmpty(subtypeId)) {
1044 subtypeId = NOT_A_SUBTYPE_ID_STR;
1045 }
1046 if (isImeAdded) {
Seigo Nonakace2c7842015-08-17 08:47:36 -07001047 builder.append(INPUT_METHOD_SEPARATOR);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001048 } else {
1049 isImeAdded = true;
1050 }
Seigo Nonakace2c7842015-08-17 08:47:36 -07001051 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001052 subtypeId);
1053 }
Seigo Nonakace2c7842015-08-17 08:47:36 -07001054 // Remove the last INPUT_METHOD_SEPARATOR
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001055 putSubtypeHistoryStr(builder.toString());
1056 }
1057
1058 private void addSubtypeToHistory(String imeId, String subtypeId) {
1059 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
1060 for (Pair<String, String> ime: subtypeHistory) {
1061 if (ime.first.equals(imeId)) {
1062 if (DEBUG) {
1063 Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
1064 + ime.second);
1065 }
1066 // We should break here
1067 subtypeHistory.remove(ime);
1068 break;
1069 }
1070 }
1071 if (DEBUG) {
1072 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
1073 }
1074 saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
1075 }
1076
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001077 private void putSubtypeHistoryStr(@NonNull String str) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001078 if (DEBUG) {
1079 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
1080 }
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001081 if (TextUtils.isEmpty(str)) {
1082 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
1083 // data scenario.
1084 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
1085 } else {
1086 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
1087 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001088 }
1089
Yohei Yukawa1c663012019-01-25 02:44:00 -08001090 Pair<String, String> getLastInputMethodAndSubtypeLocked() {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001091 // Gets the first one from the history
1092 return getLastSubtypeForInputMethodLockedInternal(null);
1093 }
1094
Yohei Yukawa1c663012019-01-25 02:44:00 -08001095 String getLastSubtypeForInputMethodLocked(String imeId) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001096 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
1097 if (ime != null) {
1098 return ime.second;
1099 } else {
1100 return null;
1101 }
1102 }
1103
1104 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
1105 List<Pair<String, ArrayList<String>>> enabledImes =
1106 getEnabledInputMethodsAndSubtypeListLocked();
1107 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
1108 for (Pair<String, String> imeAndSubtype : subtypeHistory) {
1109 final String imeInTheHistory = imeAndSubtype.first;
1110 // If imeId is empty, returns the first IME and subtype in the history
1111 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
1112 final String subtypeInTheHistory = imeAndSubtype.second;
1113 final String subtypeHashCode =
1114 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
1115 enabledImes, imeInTheHistory, subtypeInTheHistory);
1116 if (!TextUtils.isEmpty(subtypeHashCode)) {
1117 if (DEBUG) {
1118 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
1119 }
Yohei Yukawab0377bb2015-08-10 21:06:30 -07001120 return new Pair<>(imeInTheHistory, subtypeHashCode);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001121 }
1122 }
1123 }
1124 if (DEBUG) {
1125 Slog.d(TAG, "No enabled IME found in the history");
1126 }
1127 return null;
1128 }
1129
1130 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
1131 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
1132 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
1133 if (enabledIme.first.equals(imeId)) {
1134 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
1135 final InputMethodInfo imi = mMethodMap.get(imeId);
1136 if (explicitlyEnabledSubtypes.size() == 0) {
1137 // If there are no explicitly enabled subtypes, applicable subtypes are
1138 // enabled implicitly.
1139 // If IME is enabled and no subtypes are enabled, applicable subtypes
1140 // are enabled implicitly, so needs to treat them to be enabled.
1141 if (imi != null && imi.getSubtypeCount() > 0) {
1142 List<InputMethodSubtype> implicitlySelectedSubtypes =
1143 getImplicitlyApplicableSubtypesLocked(mRes, imi);
1144 if (implicitlySelectedSubtypes != null) {
1145 final int N = implicitlySelectedSubtypes.size();
1146 for (int i = 0; i < N; ++i) {
1147 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
1148 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
1149 return subtypeHashCode;
1150 }
1151 }
1152 }
1153 }
1154 } else {
1155 for (String s: explicitlyEnabledSubtypes) {
1156 if (s.equals(subtypeHashCode)) {
1157 // If both imeId and subtypeId are enabled, return subtypeId.
1158 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +01001159 final int hashCode = Integer.parseInt(subtypeHashCode);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001160 // Check whether the subtype id is valid or not
1161 if (isValidSubtypeId(imi, hashCode)) {
1162 return s;
1163 } else {
1164 return NOT_A_SUBTYPE_ID_STR;
1165 }
1166 } catch (NumberFormatException e) {
1167 return NOT_A_SUBTYPE_ID_STR;
1168 }
1169 }
1170 }
1171 }
1172 // If imeId was enabled but subtypeId was disabled.
1173 return NOT_A_SUBTYPE_ID_STR;
1174 }
1175 }
1176 // If both imeId and subtypeId are disabled, return null
1177 return null;
1178 }
1179
1180 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
Yohei Yukawab0377bb2015-08-10 21:06:30 -07001181 ArrayList<Pair<String, String>> imsList = new ArrayList<>();
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001182 final String subtypeHistoryStr = getSubtypeHistoryStr();
1183 if (TextUtils.isEmpty(subtypeHistoryStr)) {
1184 return imsList;
1185 }
1186 mInputMethodSplitter.setString(subtypeHistoryStr);
1187 while (mInputMethodSplitter.hasNext()) {
1188 String nextImsStr = mInputMethodSplitter.next();
1189 mSubtypeSplitter.setString(nextImsStr);
1190 if (mSubtypeSplitter.hasNext()) {
1191 String subtypeId = NOT_A_SUBTYPE_ID_STR;
1192 // The first element is ime id.
1193 String imeId = mSubtypeSplitter.next();
1194 while (mSubtypeSplitter.hasNext()) {
1195 subtypeId = mSubtypeSplitter.next();
1196 break;
1197 }
Yohei Yukawab0377bb2015-08-10 21:06:30 -07001198 imsList.add(new Pair<>(imeId, subtypeId));
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001199 }
1200 }
1201 return imsList;
1202 }
1203
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001204 @NonNull
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001205 private String getSubtypeHistoryStr() {
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001206 final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001207 if (DEBUG) {
Yohei Yukawa87523672016-02-12 19:37:08 -08001208 Slog.d(TAG, "getSubtypeHistoryStr: " + history);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001209 }
Yohei Yukawa87523672016-02-12 19:37:08 -08001210 return history;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001211 }
1212
Yohei Yukawa1c663012019-01-25 02:44:00 -08001213 void putSelectedInputMethod(String imeId) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001214 if (DEBUG) {
1215 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
1216 + mCurrentUserId);
1217 }
Yohei Yukawa87523672016-02-12 19:37:08 -08001218 putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001219 }
1220
Yohei Yukawa1c663012019-01-25 02:44:00 -08001221 void putSelectedSubtype(int subtypeId) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001222 if (DEBUG) {
1223 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
1224 + mCurrentUserId);
1225 }
Yohei Yukawa87523672016-02-12 19:37:08 -08001226 putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001227 }
1228
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001229 @Nullable
Yohei Yukawa1c663012019-01-25 02:44:00 -08001230 String getSelectedInputMethod() {
Yohei Yukawa7b574cb2016-03-16 17:22:22 -07001231 final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001232 if (DEBUG) {
Yohei Yukawa87523672016-02-12 19:37:08 -08001233 Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001234 }
Yohei Yukawa87523672016-02-12 19:37:08 -08001235 return imi;
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001236 }
1237
Yohei Yukawa1c663012019-01-25 02:44:00 -08001238 boolean isSubtypeSelected() {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001239 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
1240 }
1241
1242 private int getSelectedInputMethodSubtypeHashCode() {
Yohei Yukawa87523672016-02-12 19:37:08 -08001243 return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001244 }
1245
Yohei Yukawa1c663012019-01-25 02:44:00 -08001246 boolean isShowImeWithHardKeyboardEnabled() {
Yohei Yukawa87523672016-02-12 19:37:08 -08001247 return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
Michael Wright7b5a96b2014-08-09 19:28:42 -07001248 }
1249
Yohei Yukawa1c663012019-01-25 02:44:00 -08001250 void setShowImeWithHardKeyboard(boolean show) {
Yohei Yukawa87523672016-02-12 19:37:08 -08001251 putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
Michael Wright7b5a96b2014-08-09 19:28:42 -07001252 }
1253
Yohei Yukawa68645a62016-02-17 07:54:20 -08001254 @UserIdInt
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001255 public int getCurrentUserId() {
1256 return mCurrentUserId;
1257 }
1258
Yohei Yukawa1c663012019-01-25 02:44:00 -08001259 int getSelectedInputMethodSubtypeId(String selectedImiId) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001260 final InputMethodInfo imi = mMethodMap.get(selectedImiId);
1261 if (imi == null) {
1262 return NOT_A_SUBTYPE_ID;
1263 }
1264 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
1265 return getSubtypeIdFromHashCode(imi, subtypeHashCode);
1266 }
1267
Yohei Yukawa1c663012019-01-25 02:44:00 -08001268 void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
1269 InputMethodSubtype currentSubtype) {
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001270 String subtypeId = NOT_A_SUBTYPE_ID_STR;
1271 if (currentSubtype != null) {
1272 subtypeId = String.valueOf(currentSubtype.hashCode());
1273 }
1274 if (canAddToLastInputMethod(currentSubtype)) {
1275 addSubtypeToHistory(curMethodId, subtypeId);
1276 }
1277 }
Satoshi Kataokad787f692013-10-26 04:44:21 +09001278
Yohei Yukawa68645a62016-02-17 07:54:20 -08001279 public void dumpLocked(final Printer pw, final String prefix) {
1280 pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
1281 pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
1282 pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
1283 pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
1284 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001285 }
Yohei Yukawa174843a2015-06-26 18:02:54 -07001286
Yohei Yukawa1c663012019-01-25 02:44:00 -08001287 static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
1288 @StartInputFlags int startInputFlags) {
Yohei Yukawacf68d522017-12-12 09:33:26 -08001289 if (targetSdkVersion < Build.VERSION_CODES.P) {
1290 // for compatibility.
1291 return true;
1292 }
Yohei Yukawa35fa6d52018-10-31 11:33:32 -07001293 if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) {
Yohei Yukawacf68d522017-12-12 09:33:26 -08001294 return false;
1295 }
Yohei Yukawa35fa6d52018-10-31 11:33:32 -07001296 if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) {
Yohei Yukawacf68d522017-12-12 09:33:26 -08001297 return false;
1298 }
1299 return true;
1300 }
Yohei Yukawa24d8f6d2018-09-12 02:51:26 +00001301
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -08001302 /**
1303 * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
1304 * list of real user IDs.
1305 *
Yohei Yukawaa878b952019-01-10 19:36:24 -08001306 * <p>This method also converts profile user ID to profile parent user ID unless
1307 * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p>
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -08001308 *
1309 * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
1310 * {@link UserHandle#USER_ALL} are also supported
1311 * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
1312 * is specified in {@code userIdToBeResolved}.
1313 * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if
1314 * no debug message is required.
1315 * @return An integer array that contain user IDs.
1316 */
1317 static int[] resolveUserId(@UserIdInt int userIdToBeResolved,
1318 @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) {
1319 final UserManagerInternal userManagerInternal =
1320 LocalServices.getService(UserManagerInternal.class);
1321
1322 if (userIdToBeResolved == UserHandle.USER_ALL) {
Yohei Yukawaa878b952019-01-10 19:36:24 -08001323 if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
1324 return userManagerInternal.getUserIds();
1325 }
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -08001326 final IntArray result = new IntArray();
1327 for (int userId : userManagerInternal.getUserIds()) {
1328 final int parentUserId = userManagerInternal.getProfileParentId(userId);
1329 if (result.indexOf(parentUserId) < 0) {
1330 result.add(parentUserId);
1331 }
1332 }
1333 return result.toArray();
1334 }
1335
1336 final int sourceUserId;
1337 if (userIdToBeResolved == UserHandle.USER_CURRENT) {
1338 sourceUserId = currentUserId;
1339 } else if (userIdToBeResolved < 0) {
1340 if (warningWriter != null) {
1341 warningWriter.print("Pseudo user ID ");
1342 warningWriter.print(userIdToBeResolved);
1343 warningWriter.println(" is not supported.");
1344 }
1345 return new int[]{};
1346 } else if (userManagerInternal.exists(userIdToBeResolved)) {
1347 sourceUserId = userIdToBeResolved;
1348 } else {
1349 if (warningWriter != null) {
1350 warningWriter.print("User #");
1351 warningWriter.print(userIdToBeResolved);
1352 warningWriter.println(" does not exit.");
1353 }
1354 return new int[]{};
1355 }
Yohei Yukawaa878b952019-01-10 19:36:24 -08001356 final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
1357 ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId);
1358 return new int[]{resolvedUserId};
Yohei Yukawa7f8ee4b2019-01-10 08:44:57 -08001359 }
Satoshi Kataoka8e303cc2013-01-11 15:55:28 +09001360}