1 /* 2 * Copyright (C) 2024 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 17 package com.android.server.inputmethod; 18 19 import android.annotation.AnyThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.UserIdInt; 23 import android.content.pm.PackageManagerInternal; 24 import android.os.LocaleList; 25 import android.provider.Settings; 26 import android.text.TextUtils; 27 import android.util.IntArray; 28 import android.util.Pair; 29 import android.util.Printer; 30 import android.util.Slog; 31 import android.view.inputmethod.InputMethodInfo; 32 import android.view.inputmethod.InputMethodSubtype; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.function.Predicate; 39 40 /** 41 * Utility class for putting and getting settings for InputMethod. 42 * 43 * <p>This is used in two ways:</p> 44 * <ul> 45 * <li>Singleton instance in {@link InputMethodManagerService}, which is updated on 46 * user-switch to follow the current user.</li> 47 * <li>On-demand instances when we need settings for non-current users.</li> 48 * </ul> 49 */ 50 final class InputMethodSettings { 51 public static final boolean DEBUG = false; 52 private static final String TAG = "InputMethodSettings"; 53 54 /** 55 * An integer code that represents "no subtype" when a subtype hashcode is used. 56 * 57 * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have 58 * used {@code -1} here. We cannot change this value as it's already saved into secure settings. 59 * </p> 60 */ 61 static final int INVALID_SUBTYPE_HASHCODE = -1; 62 /** 63 * A string code that represents "no subtype" when a subtype hashcode is used. 64 * 65 * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have 66 * used {@code "-1"} here. We cannot change this value as it's already saved into secure 67 * settings.</p> 68 */ 69 private static final String INVALID_SUBTYPE_HASHCODE_STR = 70 String.valueOf(INVALID_SUBTYPE_HASHCODE); 71 private static final char INPUT_METHOD_SEPARATOR = InputMethodUtils.INPUT_METHOD_SEPARATOR; 72 private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = 73 InputMethodUtils.INPUT_METHOD_SUBTYPE_SEPARATOR; 74 75 private final InputMethodMap mMethodMap; 76 private final List<InputMethodInfo> mMethodList; 77 78 @UserIdInt 79 private final int mUserId; 80 buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> ime)81 private static void buildEnabledInputMethodsSettingString( 82 StringBuilder builder, Pair<String, ArrayList<String>> ime) { 83 builder.append(ime.first); 84 // Inputmethod and subtypes are saved in the settings as follows: 85 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 86 for (int i = 0; i < ime.second.size(); ++i) { 87 final String subtypeId = ime.second.get(i); 88 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId); 89 } 90 } 91 createEmptyMap(@serIdInt int userId)92 static InputMethodSettings createEmptyMap(@UserIdInt int userId) { 93 return new InputMethodSettings(InputMethodMap.emptyMap(), userId); 94 } 95 create(InputMethodMap methodMap, @UserIdInt int userId)96 static InputMethodSettings create(InputMethodMap methodMap, @UserIdInt int userId) { 97 return new InputMethodSettings(methodMap, userId); 98 } 99 InputMethodSettings(InputMethodMap methodMap, @UserIdInt int userId)100 private InputMethodSettings(InputMethodMap methodMap, @UserIdInt int userId) { 101 mMethodMap = methodMap; 102 mMethodList = methodMap.values(); 103 mUserId = userId; 104 } 105 106 @AnyThread 107 @NonNull getMethodMap()108 InputMethodMap getMethodMap() { 109 return mMethodMap; 110 } 111 112 @AnyThread 113 @NonNull getMethodList()114 List<InputMethodInfo> getMethodList() { 115 return mMethodList; 116 } 117 putString(@onNull String key, @Nullable String str)118 private void putString(@NonNull String key, @Nullable String str) { 119 SecureSettingsWrapper.putString(key, str, mUserId); 120 } 121 122 @Nullable getString(@onNull String key, @Nullable String defaultValue)123 private String getString(@NonNull String key, @Nullable String defaultValue) { 124 return SecureSettingsWrapper.getString(key, defaultValue, mUserId); 125 } 126 putInt(String key, int value)127 private void putInt(String key, int value) { 128 SecureSettingsWrapper.putInt(key, value, mUserId); 129 } 130 getInt(String key, int defaultValue)131 private int getInt(String key, int defaultValue) { 132 return SecureSettingsWrapper.getInt(key, defaultValue, mUserId); 133 } 134 getEnabledInputMethodList()135 ArrayList<InputMethodInfo> getEnabledInputMethodList() { 136 return getEnabledInputMethodListWithFilter(null /* matchingCondition */); 137 } 138 139 @NonNull getEnabledInputMethodListWithFilter( @ullable Predicate<InputMethodInfo> matchingCondition)140 ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilter( 141 @Nullable Predicate<InputMethodInfo> matchingCondition) { 142 return createEnabledInputMethodList( 143 getEnabledInputMethodsAndSubtypeList(), matchingCondition); 144 } 145 getEnabledInputMethodSubtypeList( InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes)146 List<InputMethodSubtype> getEnabledInputMethodSubtypeList( 147 InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) { 148 List<InputMethodSubtype> enabledSubtypes = 149 getEnabledInputMethodSubtypeList(imi); 150 if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) { 151 enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypes( 152 SystemLocaleWrapper.get(mUserId), imi); 153 } 154 return InputMethodSubtype.sort(imi, enabledSubtypes); 155 } 156 getEnabledInputMethodSubtypeList(InputMethodInfo imi)157 List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi) { 158 List<Pair<String, ArrayList<String>>> imsList = 159 getEnabledInputMethodsAndSubtypeList(); 160 ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); 161 if (imi != null) { 162 for (int i = 0; i < imsList.size(); ++i) { 163 final Pair<String, ArrayList<String>> imsPair = imsList.get(i); 164 final InputMethodInfo info = mMethodMap.get(imsPair.first); 165 if (info != null && info.getId().equals(imi.getId())) { 166 final int subtypeCount = info.getSubtypeCount(); 167 for (int j = 0; j < subtypeCount; ++j) { 168 final InputMethodSubtype ims = info.getSubtypeAt(j); 169 for (int k = 0; k < imsPair.second.size(); ++k) { 170 final String s = imsPair.second.get(k); 171 if (String.valueOf(ims.hashCode()).equals(s)) { 172 enabledSubtypes.add(ims); 173 } 174 } 175 } 176 break; 177 } 178 } 179 } 180 return enabledSubtypes; 181 } 182 getEnabledInputMethodsAndSubtypeList()183 List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeList() { 184 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 185 final TextUtils.SimpleStringSplitter inputMethodSplitter = 186 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); 187 final TextUtils.SimpleStringSplitter subtypeSplitter = 188 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); 189 final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>(); 190 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 191 return imsList; 192 } 193 inputMethodSplitter.setString(enabledInputMethodsStr); 194 while (inputMethodSplitter.hasNext()) { 195 String nextImsStr = inputMethodSplitter.next(); 196 subtypeSplitter.setString(nextImsStr); 197 if (subtypeSplitter.hasNext()) { 198 ArrayList<String> subtypeHashes = new ArrayList<>(); 199 // The first element is ime id. 200 String imeId = subtypeSplitter.next(); 201 while (subtypeSplitter.hasNext()) { 202 subtypeHashes.add(subtypeSplitter.next()); 203 } 204 imsList.add(new Pair<>(imeId, subtypeHashes)); 205 } 206 } 207 return imsList; 208 } 209 210 /** 211 * Build and put a string of EnabledInputMethods with removing specified Id. 212 * 213 * @return the specified id was removed or not. 214 */ buildAndPutEnabledInputMethodsStrRemovingId( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id)215 boolean buildAndPutEnabledInputMethodsStrRemovingId( 216 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 217 boolean isRemoved = false; 218 boolean needsAppendSeparator = false; 219 for (int i = 0; i < imsList.size(); ++i) { 220 final Pair<String, ArrayList<String>> ims = imsList.get(i); 221 final String curId = ims.first; 222 if (curId.equals(id)) { 223 // We are disabling this input method, and it is 224 // currently enabled. Skip it to remove from the 225 // new list. 226 isRemoved = true; 227 } else { 228 if (needsAppendSeparator) { 229 builder.append(INPUT_METHOD_SEPARATOR); 230 } else { 231 needsAppendSeparator = true; 232 } 233 buildEnabledInputMethodsSettingString(builder, ims); 234 } 235 } 236 if (isRemoved) { 237 // Update the setting with the new list of input methods. 238 putEnabledInputMethodsStr(builder.toString()); 239 } 240 return isRemoved; 241 } 242 createEnabledInputMethodList( List<Pair<String, ArrayList<String>>> imsList, Predicate<InputMethodInfo> matchingCondition)243 private ArrayList<InputMethodInfo> createEnabledInputMethodList( 244 List<Pair<String, ArrayList<String>>> imsList, 245 Predicate<InputMethodInfo> matchingCondition) { 246 final ArrayList<InputMethodInfo> res = new ArrayList<>(); 247 for (int i = 0; i < imsList.size(); ++i) { 248 final Pair<String, ArrayList<String>> ims = imsList.get(i); 249 final InputMethodInfo info = mMethodMap.get(ims.first); 250 if (info != null && !info.isVrOnly() 251 && (matchingCondition == null || matchingCondition.test(info))) { 252 res.add(info); 253 } 254 } 255 return res; 256 } 257 putEnabledInputMethodsStr(@ullable String str)258 void putEnabledInputMethodsStr(@Nullable String str) { 259 if (DEBUG) { 260 Slog.d(TAG, "putEnabledInputMethodStr: " + str); 261 } 262 if (TextUtils.isEmpty(str)) { 263 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the 264 // empty data scenario. 265 putString(Settings.Secure.ENABLED_INPUT_METHODS, null); 266 } else { 267 putString(Settings.Secure.ENABLED_INPUT_METHODS, str); 268 } 269 } 270 271 @NonNull getEnabledInputMethodsStr()272 String getEnabledInputMethodsStr() { 273 return getString(Settings.Secure.ENABLED_INPUT_METHODS, ""); 274 } 275 saveSubtypeHistory( List<Pair<String, String>> savedImes, String newImeId, String newSubtypeHashCodeStr)276 private void saveSubtypeHistory( 277 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeHashCodeStr) { 278 final StringBuilder builder = new StringBuilder(); 279 boolean isImeAdded = false; 280 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeHashCodeStr)) { 281 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append( 282 newSubtypeHashCodeStr); 283 isImeAdded = true; 284 } 285 for (int i = 0; i < savedImes.size(); ++i) { 286 final Pair<String, String> ime = savedImes.get(i); 287 final String imeId = ime.first; 288 String subtypeHashCodeStr = ime.second; 289 if (TextUtils.isEmpty(subtypeHashCodeStr)) { 290 subtypeHashCodeStr = INVALID_SUBTYPE_HASHCODE_STR; 291 } 292 if (isImeAdded) { 293 builder.append(INPUT_METHOD_SEPARATOR); 294 } else { 295 isImeAdded = true; 296 } 297 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeHashCodeStr); 298 } 299 // Remove the last INPUT_METHOD_SEPARATOR 300 putSubtypeHistoryStr(builder.toString()); 301 } 302 addSubtypeToHistory(String imeId, String subtypeHashCodeStr)303 private void addSubtypeToHistory(String imeId, String subtypeHashCodeStr) { 304 final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory(); 305 for (int i = 0; i < subtypeHistory.size(); ++i) { 306 final Pair<String, String> ime = subtypeHistory.get(i); 307 if (ime.first.equals(imeId)) { 308 if (DEBUG) { 309 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 310 + ime.second); 311 } 312 // We should break here 313 subtypeHistory.remove(ime); 314 break; 315 } 316 } 317 if (DEBUG) { 318 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeHashCodeStr); 319 } 320 saveSubtypeHistory(subtypeHistory, imeId, subtypeHashCodeStr); 321 } 322 putSubtypeHistoryStr(@onNull String str)323 private void putSubtypeHistoryStr(@NonNull String str) { 324 if (DEBUG) { 325 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 326 } 327 if (TextUtils.isEmpty(str)) { 328 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty 329 // data scenario. 330 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null); 331 } else { 332 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 333 } 334 } 335 getLastInputMethodAndSubtype()336 Pair<String, String> getLastInputMethodAndSubtype() { 337 // Gets the first one from the history 338 return getLastSubtypeForInputMethodInternal(null); 339 } 340 341 @Nullable getLastInputMethodSubtype()342 InputMethodSubtype getLastInputMethodSubtype() { 343 final Pair<String, String> lastIme = getLastInputMethodAndSubtype(); 344 // TODO: Handle the case of the last IME with no subtypes 345 if (lastIme == null || TextUtils.isEmpty(lastIme.first) 346 || TextUtils.isEmpty(lastIme.second)) { 347 return null; 348 } 349 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); 350 if (lastImi == null) return null; 351 try { 352 final int lastSubtypeHash = Integer.parseInt(lastIme.second); 353 final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, 354 lastSubtypeHash); 355 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { 356 return null; 357 } 358 return lastImi.getSubtypeAt(lastSubtypeId); 359 } catch (NumberFormatException e) { 360 return null; 361 } 362 } 363 getLastSubtypeForInputMethod(String imeId)364 String getLastSubtypeForInputMethod(String imeId) { 365 Pair<String, String> ime = getLastSubtypeForInputMethodInternal(imeId); 366 if (ime != null) { 367 return ime.second; 368 } else { 369 return null; 370 } 371 } 372 getLastSubtypeForInputMethodInternal(String imeId)373 private Pair<String, String> getLastSubtypeForInputMethodInternal(String imeId) { 374 final List<Pair<String, ArrayList<String>>> enabledImes = 375 getEnabledInputMethodsAndSubtypeList(); 376 final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory(); 377 for (int i = 0; i < subtypeHistory.size(); ++i) { 378 final Pair<String, String> imeAndSubtype = subtypeHistory.get(i); 379 final String imeInTheHistory = imeAndSubtype.first; 380 // If imeId is empty, returns the first IME and subtype in the history 381 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 382 final String subtypeInTheHistory = imeAndSubtype.second; 383 final String subtypeHashCode = 384 getEnabledSubtypeHashCodeForInputMethodAndSubtype( 385 enabledImes, imeInTheHistory, subtypeInTheHistory); 386 if (!TextUtils.isEmpty(subtypeHashCode)) { 387 if (DEBUG) { 388 Slog.d(TAG, 389 "Enabled subtype found in the history: " + subtypeHashCode); 390 } 391 return new Pair<>(imeInTheHistory, subtypeHashCode); 392 } 393 } 394 } 395 if (DEBUG) { 396 Slog.d(TAG, "No enabled IME found in the history"); 397 } 398 return null; 399 } 400 getEnabledSubtypeHashCodeForInputMethodAndSubtype(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode)401 private String getEnabledSubtypeHashCodeForInputMethodAndSubtype(List<Pair<String, 402 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 403 final LocaleList localeList = SystemLocaleWrapper.get(mUserId); 404 for (int i = 0; i < enabledImes.size(); ++i) { 405 final Pair<String, ArrayList<String>> enabledIme = enabledImes.get(i); 406 if (enabledIme.first.equals(imeId)) { 407 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 408 final InputMethodInfo imi = mMethodMap.get(imeId); 409 if (explicitlyEnabledSubtypes.isEmpty()) { 410 // If there are no explicitly enabled subtypes, applicable subtypes are 411 // enabled implicitly. 412 // If IME is enabled and no subtypes are enabled, applicable subtypes 413 // are enabled implicitly, so needs to treat them to be enabled. 414 if (imi != null && imi.getSubtypeCount() > 0) { 415 List<InputMethodSubtype> implicitlyEnabledSubtypes = 416 SubtypeUtils.getImplicitlyApplicableSubtypes(localeList, 417 imi); 418 final int numSubtypes = implicitlyEnabledSubtypes.size(); 419 for (int j = 0; j < numSubtypes; ++j) { 420 final InputMethodSubtype st = implicitlyEnabledSubtypes.get(j); 421 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 422 return subtypeHashCode; 423 } 424 } 425 } 426 } else { 427 for (int j = 0; j < explicitlyEnabledSubtypes.size(); ++j) { 428 final String s = explicitlyEnabledSubtypes.get(j); 429 if (s.equals(subtypeHashCode)) { 430 // If both imeId and subtype are enabled, return subtypeId. 431 try { 432 final int hashCode = Integer.parseInt(subtypeHashCode); 433 // Check whether the subtype is valid or not 434 if (SubtypeUtils.isValidSubtypeHashCode(imi, hashCode)) { 435 return s; 436 } else { 437 return INVALID_SUBTYPE_HASHCODE_STR; 438 } 439 } catch (NumberFormatException e) { 440 return INVALID_SUBTYPE_HASHCODE_STR; 441 } 442 } 443 } 444 } 445 // If imeId was enabled but subtype was disabled. 446 return INVALID_SUBTYPE_HASHCODE_STR; 447 } 448 } 449 // If both imeId and subtype are disabled, return null 450 return null; 451 } 452 loadInputMethodAndSubtypeHistory()453 private List<Pair<String, String>> loadInputMethodAndSubtypeHistory() { 454 ArrayList<Pair<String, String>> imsList = new ArrayList<>(); 455 final String subtypeHistoryStr = getSubtypeHistoryStr(); 456 if (TextUtils.isEmpty(subtypeHistoryStr)) { 457 return imsList; 458 } 459 final TextUtils.SimpleStringSplitter inputMethodSplitter = 460 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); 461 final TextUtils.SimpleStringSplitter subtypeSplitter = 462 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); 463 inputMethodSplitter.setString(subtypeHistoryStr); 464 while (inputMethodSplitter.hasNext()) { 465 String nextImsStr = inputMethodSplitter.next(); 466 subtypeSplitter.setString(nextImsStr); 467 if (subtypeSplitter.hasNext()) { 468 String subtypeHashCodeStr = INVALID_SUBTYPE_HASHCODE_STR; 469 // The first element is ime id. 470 String imeId = subtypeSplitter.next(); 471 while (subtypeSplitter.hasNext()) { 472 subtypeHashCodeStr = subtypeSplitter.next(); 473 break; 474 } 475 imsList.add(new Pair<>(imeId, subtypeHashCodeStr)); 476 } 477 } 478 return imsList; 479 } 480 481 @NonNull getSubtypeHistoryStr()482 private String getSubtypeHistoryStr() { 483 final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, ""); 484 if (DEBUG) { 485 Slog.d(TAG, "getSubtypeHistoryStr: " + history); 486 } 487 return history; 488 } 489 putSelectedInputMethod(String imeId)490 void putSelectedInputMethod(String imeId) { 491 if (DEBUG) { 492 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mUserId); 493 } 494 putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 495 } 496 putSelectedSubtype(int subtypeId)497 void putSelectedSubtype(int subtypeId) { 498 if (DEBUG) { 499 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId); 500 } 501 putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 502 } 503 504 @Nullable getSelectedInputMethod()505 String getSelectedInputMethod() { 506 final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null); 507 if (DEBUG) { 508 Slog.d(TAG, "getSelectedInputMethodStr: " + imi); 509 } 510 return imi; 511 } 512 513 @Nullable getSelectedDefaultDeviceInputMethod()514 String getSelectedDefaultDeviceInputMethod() { 515 final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null); 516 if (DEBUG) { 517 Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", " + mUserId); 518 } 519 return imi; 520 } 521 putSelectedDefaultDeviceInputMethod(String imeId)522 void putSelectedDefaultDeviceInputMethod(String imeId) { 523 if (DEBUG) { 524 Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", " + mUserId); 525 } 526 putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId); 527 } 528 putDefaultVoiceInputMethod(String imeId)529 void putDefaultVoiceInputMethod(String imeId) { 530 if (DEBUG) { 531 Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mUserId); 532 } 533 putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId); 534 } 535 536 @Nullable getDefaultVoiceInputMethod()537 String getDefaultVoiceInputMethod() { 538 final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null); 539 if (DEBUG) { 540 Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi); 541 } 542 return imi; 543 } 544 getSelectedInputMethodSubtypeHashCode()545 private int getSelectedInputMethodSubtypeHashCode() { 546 return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, INVALID_SUBTYPE_HASHCODE); 547 } 548 549 @UserIdInt getUserId()550 public int getUserId() { 551 return mUserId; 552 } 553 getSelectedInputMethodSubtypeId(String selectedImiId)554 int getSelectedInputMethodSubtypeId(String selectedImiId) { 555 final InputMethodInfo imi = mMethodMap.get(selectedImiId); 556 if (imi == null) { 557 return InputMethodUtils.NOT_A_SUBTYPE_ID; 558 } 559 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 560 return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode); 561 } 562 saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, InputMethodSubtype currentSubtype)563 void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, 564 InputMethodSubtype currentSubtype) { 565 String subtypeHashCodeStr = INVALID_SUBTYPE_HASHCODE_STR; 566 if (currentSubtype != null) { 567 subtypeHashCodeStr = String.valueOf(currentSubtype.hashCode()); 568 } 569 if (InputMethodUtils.canAddToLastInputMethod(currentSubtype)) { 570 addSubtypeToHistory(curMethodId, subtypeHashCodeStr); 571 } 572 } 573 574 /** 575 * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for 576 * non-current users. 577 * 578 * <p>TODO: Address code duplication between this and 579 * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p> 580 * 581 * @return {@link InputMethodSubtype} if exists. {@code null} otherwise. 582 */ 583 @Nullable getCurrentInputMethodSubtypeForNonCurrentUsers()584 InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() { 585 final String selectedMethodId = getSelectedInputMethod(); 586 if (selectedMethodId == null) { 587 return null; 588 } 589 final InputMethodInfo imi = mMethodMap.get(selectedMethodId); 590 if (imi == null || imi.getSubtypeCount() == 0) { 591 return null; 592 } 593 594 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 595 if (subtypeHashCode != INVALID_SUBTYPE_HASHCODE) { 596 final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi, 597 subtypeHashCode); 598 if (subtypeIndex >= 0) { 599 return imi.getSubtypeAt(subtypeIndex); 600 } 601 } 602 603 // If there are no selected subtypes, the framework will try to find the most applicable 604 // subtype from explicitly or implicitly enabled subtypes. 605 final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 606 getEnabledInputMethodSubtypeList(imi, true); 607 // If there is only one explicitly or implicitly enabled subtype, just returns it. 608 if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) { 609 return null; 610 } 611 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 612 return explicitlyOrImplicitlyEnabledSubtypes.get(0); 613 } 614 final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString(); 615 final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype( 616 explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, 617 locale, true); 618 if (subtype != null) { 619 return subtype; 620 } 621 return SubtypeUtils.findLastResortApplicableSubtype( 622 explicitlyOrImplicitlyEnabledSubtypes, null, locale, true); 623 } 624 625 @NonNull getNewAdditionalSubtypeMap(@onNull String imeId, @NonNull ArrayList<InputMethodSubtype> subtypes, @NonNull AdditionalSubtypeMap additionalSubtypeMap, @NonNull PackageManagerInternal packageManagerInternal, int callingUid)626 AdditionalSubtypeMap getNewAdditionalSubtypeMap(@NonNull String imeId, 627 @NonNull ArrayList<InputMethodSubtype> subtypes, 628 @NonNull AdditionalSubtypeMap additionalSubtypeMap, 629 @NonNull PackageManagerInternal packageManagerInternal, int callingUid) { 630 final InputMethodInfo imi = mMethodMap.get(imeId); 631 if (imi == null) { 632 return additionalSubtypeMap; 633 } 634 if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid, 635 imi.getPackageName())) { 636 return additionalSubtypeMap; 637 } 638 639 final AdditionalSubtypeMap newMap; 640 if (subtypes.isEmpty()) { 641 newMap = additionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId()); 642 } else { 643 newMap = additionalSubtypeMap.cloneWithPut(imi.getId(), subtypes); 644 } 645 return newMap; 646 } 647 setEnabledInputMethodSubtypes(@onNull String imeId, @NonNull int[] subtypeHashCodes)648 boolean setEnabledInputMethodSubtypes(@NonNull String imeId, 649 @NonNull int[] subtypeHashCodes) { 650 final InputMethodInfo imi = mMethodMap.get(imeId); 651 if (imi == null) { 652 return false; 653 } 654 655 final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length); 656 for (int subtypeHashCode : subtypeHashCodes) { 657 if (subtypeHashCode == INVALID_SUBTYPE_HASHCODE) { 658 continue; // INVALID_SUBTYPE_HASHCODE must not be saved 659 } 660 if (!SubtypeUtils.isValidSubtypeHashCode(imi, subtypeHashCode)) { 661 continue; // this subtype does not exist in InputMethodInfo. 662 } 663 if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) { 664 continue; // The entry is already added. No need to add anymore. 665 } 666 validSubtypeHashCodes.add(subtypeHashCode); 667 } 668 669 final String originalEnabledImesString = getEnabledInputMethodsStr(); 670 final String updatedEnabledImesString = updateEnabledImeString( 671 originalEnabledImesString, imi.getId(), validSubtypeHashCodes); 672 if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) { 673 return false; 674 } 675 676 putEnabledInputMethodsStr(updatedEnabledImesString); 677 return true; 678 } 679 680 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) updateEnabledImeString(@onNull String enabledImesString, @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes)681 static String updateEnabledImeString(@NonNull String enabledImesString, 682 @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) { 683 final TextUtils.SimpleStringSplitter imeSplitter = 684 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); 685 final TextUtils.SimpleStringSplitter imeSubtypeSplitter = 686 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); 687 688 final StringBuilder sb = new StringBuilder(); 689 690 imeSplitter.setString(enabledImesString); 691 boolean needsImeSeparator = false; 692 while (imeSplitter.hasNext()) { 693 final String nextImsStr = imeSplitter.next(); 694 imeSubtypeSplitter.setString(nextImsStr); 695 if (imeSubtypeSplitter.hasNext()) { 696 if (needsImeSeparator) { 697 sb.append(INPUT_METHOD_SEPARATOR); 698 } 699 if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) { 700 sb.append(imeId); 701 for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) { 702 sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR); 703 sb.append(enabledSubtypeHashCodes.get(i)); 704 } 705 } else { 706 sb.append(nextImsStr); 707 } 708 needsImeSeparator = true; 709 } 710 } 711 return sb.toString(); 712 } 713 dump(final Printer pw, final String prefix)714 void dump(final Printer pw, final String prefix) { 715 pw.println(prefix + "mUserId=" + mUserId); 716 } 717 } 718