1 /* 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 17 package com.android.server.inputmethod; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserHandleAware; 22 import android.annotation.UserIdInt; 23 import android.content.ComponentName; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManagerInternal; 29 import android.content.pm.ResolveInfo; 30 import android.content.res.Resources; 31 import android.os.Build; 32 import android.os.UserHandle; 33 import android.provider.Settings; 34 import android.text.TextUtils; 35 import android.util.ArrayMap; 36 import android.util.ArraySet; 37 import android.util.IntArray; 38 import android.util.Pair; 39 import android.util.Printer; 40 import android.util.Slog; 41 import android.view.inputmethod.InputMethodInfo; 42 import android.view.inputmethod.InputMethodSubtype; 43 import android.view.textservice.SpellCheckerInfo; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.inputmethod.StartInputFlags; 47 import com.android.server.LocalServices; 48 import com.android.server.pm.UserManagerInternal; 49 import com.android.server.textservices.TextServicesManagerInternal; 50 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.List; 55 import java.util.function.Predicate; 56 57 /** 58 * This class provides random static utility methods for {@link InputMethodManagerService} and its 59 * utility classes. 60 * 61 * <p>This class is intentionally package-private. Utility methods here are tightly coupled with 62 * implementation details in {@link InputMethodManagerService}. Hence this class is not suitable 63 * for other components to directly use.</p> 64 */ 65 final class InputMethodUtils { 66 public static final boolean DEBUG = false; 67 static final int NOT_A_SUBTYPE_ID = -1; 68 private static final String TAG = "InputMethodUtils"; 69 private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); 70 71 // The string for enabled input method is saved as follows: 72 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 73 private static final char INPUT_METHOD_SEPARATOR = ':'; 74 private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';'; 75 InputMethodUtils()76 private InputMethodUtils() { 77 // This utility class is not publicly instantiable. 78 } 79 80 // ---------------------------------------------------------------------- 81 canAddToLastInputMethod(InputMethodSubtype subtype)82 static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { 83 if (subtype == null) return true; 84 return !subtype.isAuxiliary(); 85 } 86 87 @UserHandleAware setNonSelectedSystemImesDisabledUntilUsed(PackageManager packageManagerForUser, List<InputMethodInfo> enabledImis)88 static void setNonSelectedSystemImesDisabledUntilUsed(PackageManager packageManagerForUser, 89 List<InputMethodInfo> enabledImis) { 90 if (DEBUG) { 91 Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); 92 } 93 final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray( 94 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes); 95 if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) { 96 return; 97 } 98 // Only the current spell checker should be treated as an enabled one. 99 final SpellCheckerInfo currentSpellChecker = 100 TextServicesManagerInternal.get().getCurrentSpellCheckerForUser( 101 packageManagerForUser.getUserId()); 102 for (final String packageName : systemImesDisabledUntilUsed) { 103 if (DEBUG) { 104 Slog.d(TAG, "check " + packageName); 105 } 106 boolean enabledIme = false; 107 for (int j = 0; j < enabledImis.size(); ++j) { 108 final InputMethodInfo imi = enabledImis.get(j); 109 if (packageName.equals(imi.getPackageName())) { 110 enabledIme = true; 111 break; 112 } 113 } 114 if (enabledIme) { 115 // enabled ime. skip 116 continue; 117 } 118 if (currentSpellChecker != null 119 && packageName.equals(currentSpellChecker.getPackageName())) { 120 // enabled spell checker. skip 121 if (DEBUG) { 122 Slog.d(TAG, packageName + " is the current spell checker. skip"); 123 } 124 continue; 125 } 126 ApplicationInfo ai; 127 try { 128 ai = packageManagerForUser.getApplicationInfo(packageName, 129 PackageManager.ApplicationInfoFlags.of( 130 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS)); 131 } catch (PackageManager.NameNotFoundException e) { 132 // This is not an error. No need to show scary error messages. 133 if (DEBUG) { 134 Slog.d(TAG, packageName 135 + " does not exist for userId=" + packageManagerForUser.getUserId()); 136 } 137 continue; 138 } 139 if (ai == null) { 140 // No app found for packageName 141 continue; 142 } 143 final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 144 if (!isSystemPackage) { 145 continue; 146 } 147 setDisabledUntilUsed(packageManagerForUser, packageName); 148 } 149 } 150 setDisabledUntilUsed(PackageManager packageManagerForUser, String packageName)151 private static void setDisabledUntilUsed(PackageManager packageManagerForUser, 152 String packageName) { 153 final int state; 154 try { 155 state = packageManagerForUser.getApplicationEnabledSetting(packageName); 156 } catch (IllegalArgumentException e) { 157 Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName 158 + " userId=" + packageManagerForUser.getUserId(), e); 159 return; 160 } 161 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 162 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 163 if (DEBUG) { 164 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED"); 165 } 166 try { 167 packageManagerForUser.setApplicationEnabledSetting(packageName, 168 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 169 0 /* newState */); 170 } catch (IllegalArgumentException e) { 171 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName 172 + " userId=" + packageManagerForUser.getUserId(), e); 173 return; 174 } 175 } else { 176 if (DEBUG) { 177 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED"); 178 } 179 } 180 } 181 182 /** 183 * Returns true if a package name belongs to a UID. 184 * 185 * <p>This is a simple wrapper of 186 * {@link PackageManagerInternal#getPackageUid(String, long, int)}.</p> 187 * @param packageManagerInternal the {@link PackageManagerInternal} object to be used for the 188 * validation. 189 * @param uid the UID to be validated. 190 * @param packageName the package name. 191 * @return {@code true} if the package name belongs to the UID. 192 */ checkIfPackageBelongsToUid(PackageManagerInternal packageManagerInternal, int uid, String packageName)193 static boolean checkIfPackageBelongsToUid(PackageManagerInternal packageManagerInternal, 194 int uid, String packageName) { 195 // PackageManagerInternal#getPackageUid() doesn't check MATCH_INSTANT/MATCH_APEX as of 196 // writing. So setting 0 should be fine. 197 return packageManagerInternal.isSameApp(packageName, /* flags= */ 0, uid, 198 UserHandle.getUserId(uid)); 199 } 200 201 /** 202 * Utility class for putting and getting settings for InputMethod. 203 * 204 * This is used in two ways: 205 * - Singleton instance in {@link InputMethodManagerService}, which is updated on user-switch to 206 * follow the current user. 207 * - On-demand instances when we need settings for non-current users. 208 * 209 * TODO: Move all putters and getters of settings to this class. 210 */ 211 @UserHandleAware 212 public static class InputMethodSettings { 213 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 214 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); 215 216 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 217 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); 218 219 @NonNull 220 private Context mUserAwareContext; 221 private Resources mRes; 222 private ContentResolver mResolver; 223 private final ArrayMap<String, InputMethodInfo> mMethodMap; 224 225 /** 226 * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. 227 */ 228 private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); 229 230 private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); 231 static { 232 Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); 233 } 234 235 private static final UserManagerInternal sUserManagerInternal = 236 LocalServices.getService(UserManagerInternal.class); 237 238 private boolean mCopyOnWrite = false; 239 @NonNull 240 private String mEnabledInputMethodsStrCache = ""; 241 @UserIdInt 242 private int mCurrentUserId; 243 private int[] mCurrentProfileIds = new int[0]; 244 buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> ime)245 private static void buildEnabledInputMethodsSettingString( 246 StringBuilder builder, Pair<String, ArrayList<String>> ime) { 247 builder.append(ime.first); 248 // Inputmethod and subtypes are saved in the settings as follows: 249 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 250 for (String subtypeId: ime.second) { 251 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId); 252 } 253 } 254 buildInputMethodsAndSubtypeList( String enabledInputMethodsStr, TextUtils.SimpleStringSplitter inputMethodSplitter, TextUtils.SimpleStringSplitter subtypeSplitter)255 private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList( 256 String enabledInputMethodsStr, 257 TextUtils.SimpleStringSplitter inputMethodSplitter, 258 TextUtils.SimpleStringSplitter subtypeSplitter) { 259 ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>(); 260 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 261 return imsList; 262 } 263 inputMethodSplitter.setString(enabledInputMethodsStr); 264 while (inputMethodSplitter.hasNext()) { 265 String nextImsStr = inputMethodSplitter.next(); 266 subtypeSplitter.setString(nextImsStr); 267 if (subtypeSplitter.hasNext()) { 268 ArrayList<String> subtypeHashes = new ArrayList<>(); 269 // The first element is ime id. 270 String imeId = subtypeSplitter.next(); 271 while (subtypeSplitter.hasNext()) { 272 subtypeHashes.add(subtypeSplitter.next()); 273 } 274 imsList.add(new Pair<>(imeId, subtypeHashes)); 275 } 276 } 277 return imsList; 278 } 279 initContentWithUserContext(@onNull Context context, @UserIdInt int userId)280 private void initContentWithUserContext(@NonNull Context context, @UserIdInt int userId) { 281 mUserAwareContext = context.getUserId() == userId 282 ? context 283 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); 284 mRes = mUserAwareContext.getResources(); 285 mResolver = mUserAwareContext.getContentResolver(); 286 } 287 InputMethodSettings(@onNull Context context, ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, boolean copyOnWrite)288 InputMethodSettings(@NonNull Context context, 289 ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, 290 boolean copyOnWrite) { 291 mMethodMap = methodMap; 292 initContentWithUserContext(context, userId); 293 switchCurrentUser(userId, copyOnWrite); 294 } 295 296 /** 297 * Must be called when the current user is changed. 298 * 299 * @param userId The user ID. 300 * @param copyOnWrite If {@code true}, for each settings key 301 * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual 302 * settings on the {@link Settings.Secure} until we do the first write operation. 303 */ switchCurrentUser(@serIdInt int userId, boolean copyOnWrite)304 void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { 305 if (DEBUG) { 306 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); 307 } 308 if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) { 309 mCopyOnWriteDataStore.clear(); 310 mEnabledInputMethodsStrCache = ""; 311 // TODO: mCurrentProfileIds should be cleared here. 312 } 313 if (mUserAwareContext.getUserId() != userId) { 314 initContentWithUserContext(mUserAwareContext, userId); 315 } 316 mCurrentUserId = userId; 317 mCopyOnWrite = copyOnWrite; 318 // TODO: mCurrentProfileIds should be updated here. 319 } 320 putString(@onNull String key, @Nullable String str)321 private void putString(@NonNull String key, @Nullable String str) { 322 if (mCopyOnWrite) { 323 mCopyOnWriteDataStore.put(key, str); 324 } else { 325 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) 326 ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; 327 Settings.Secure.putStringForUser(mResolver, key, str, userId); 328 } 329 } 330 331 @Nullable getString(@onNull String key, @Nullable String defaultValue)332 private String getString(@NonNull String key, @Nullable String defaultValue) { 333 return getStringForUser(key, defaultValue, mCurrentUserId); 334 } 335 336 @Nullable getStringForUser( @onNull String key, @Nullable String defaultValue, @UserIdInt int userId)337 private String getStringForUser( 338 @NonNull String key, @Nullable String defaultValue, @UserIdInt int userId) { 339 final String result; 340 if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { 341 result = mCopyOnWriteDataStore.get(key); 342 } else { 343 result = Settings.Secure.getStringForUser(mResolver, key, userId); 344 } 345 return result != null ? result : defaultValue; 346 } 347 putInt(String key, int value)348 private void putInt(String key, int value) { 349 if (mCopyOnWrite) { 350 mCopyOnWriteDataStore.put(key, String.valueOf(value)); 351 } else { 352 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) 353 ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; 354 Settings.Secure.putIntForUser(mResolver, key, value, userId); 355 } 356 } 357 getInt(String key, int defaultValue)358 private int getInt(String key, int defaultValue) { 359 if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { 360 final String result = mCopyOnWriteDataStore.get(key); 361 return result != null ? Integer.parseInt(result) : defaultValue; 362 } 363 return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); 364 } 365 putBoolean(String key, boolean value)366 private void putBoolean(String key, boolean value) { 367 putInt(key, value ? 1 : 0); 368 } 369 getBoolean(String key, boolean defaultValue)370 private boolean getBoolean(String key, boolean defaultValue) { 371 return getInt(key, defaultValue ? 1 : 0) == 1; 372 } 373 setCurrentProfileIds(int[] currentProfileIds)374 public void setCurrentProfileIds(int[] currentProfileIds) { 375 synchronized (this) { 376 mCurrentProfileIds = currentProfileIds; 377 } 378 } 379 isCurrentProfile(int userId)380 public boolean isCurrentProfile(int userId) { 381 synchronized (this) { 382 if (userId == mCurrentUserId) return true; 383 for (int i = 0; i < mCurrentProfileIds.length; i++) { 384 if (userId == mCurrentProfileIds[i]) return true; 385 } 386 return false; 387 } 388 } 389 getEnabledInputMethodListLocked()390 ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { 391 return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */); 392 } 393 394 @NonNull getEnabledInputMethodListWithFilterLocked( @ullable Predicate<InputMethodInfo> matchingCondition)395 ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked( 396 @Nullable Predicate<InputMethodInfo> matchingCondition) { 397 return createEnabledInputMethodListLocked( 398 getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition); 399 } 400 getEnabledInputMethodSubtypeListLocked( InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes)401 List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 402 InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) { 403 List<InputMethodSubtype> enabledSubtypes = 404 getEnabledInputMethodSubtypeListLocked(imi); 405 if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) { 406 enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi); 407 } 408 return InputMethodSubtype.sort(imi, enabledSubtypes); 409 } 410 getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi)411 List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) { 412 List<Pair<String, ArrayList<String>>> imsList = 413 getEnabledInputMethodsAndSubtypeListLocked(); 414 ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); 415 if (imi != null) { 416 for (Pair<String, ArrayList<String>> imsPair : imsList) { 417 InputMethodInfo info = mMethodMap.get(imsPair.first); 418 if (info != null && info.getId().equals(imi.getId())) { 419 final int subtypeCount = info.getSubtypeCount(); 420 for (int i = 0; i < subtypeCount; ++i) { 421 InputMethodSubtype ims = info.getSubtypeAt(i); 422 for (String s: imsPair.second) { 423 if (String.valueOf(ims.hashCode()).equals(s)) { 424 enabledSubtypes.add(ims); 425 } 426 } 427 } 428 break; 429 } 430 } 431 } 432 return enabledSubtypes; 433 } 434 getEnabledInputMethodsAndSubtypeListLocked()435 List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 436 return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), 437 mInputMethodSplitter, 438 mSubtypeSplitter); 439 } 440 getEnabledInputMethodNames()441 List<String> getEnabledInputMethodNames() { 442 List<String> result = new ArrayList<>(); 443 for (Pair<String, ArrayList<String>> pair : 444 getEnabledInputMethodsAndSubtypeListLocked()) { 445 result.add(pair.first); 446 } 447 return result; 448 } 449 appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr)450 void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 451 if (reloadInputMethodStr) { 452 getEnabledInputMethodsStr(); 453 } 454 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 455 // Add in the newly enabled input method. 456 putEnabledInputMethodsStr(id); 457 } else { 458 putEnabledInputMethodsStr( 459 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATOR + id); 460 } 461 } 462 463 /** 464 * Build and put a string of EnabledInputMethods with removing specified Id. 465 * @return the specified id was removed or not. 466 */ buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id)467 boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 468 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 469 boolean isRemoved = false; 470 boolean needsAppendSeparator = false; 471 for (Pair<String, ArrayList<String>> ims: imsList) { 472 String curId = ims.first; 473 if (curId.equals(id)) { 474 // We are disabling this input method, and it is 475 // currently enabled. Skip it to remove from the 476 // new list. 477 isRemoved = true; 478 } else { 479 if (needsAppendSeparator) { 480 builder.append(INPUT_METHOD_SEPARATOR); 481 } else { 482 needsAppendSeparator = true; 483 } 484 buildEnabledInputMethodsSettingString(builder, ims); 485 } 486 } 487 if (isRemoved) { 488 // Update the setting with the new list of input methods. 489 putEnabledInputMethodsStr(builder.toString()); 490 } 491 return isRemoved; 492 } 493 createEnabledInputMethodListLocked( List<Pair<String, ArrayList<String>>> imsList, Predicate<InputMethodInfo> matchingCondition)494 private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked( 495 List<Pair<String, ArrayList<String>>> imsList, 496 Predicate<InputMethodInfo> matchingCondition) { 497 final ArrayList<InputMethodInfo> res = new ArrayList<>(); 498 for (Pair<String, ArrayList<String>> ims: imsList) { 499 InputMethodInfo info = mMethodMap.get(ims.first); 500 if (info != null && !info.isVrOnly() 501 && (matchingCondition == null || matchingCondition.test(info))) { 502 res.add(info); 503 } 504 } 505 return res; 506 } 507 putEnabledInputMethodsStr(@ullable String str)508 void putEnabledInputMethodsStr(@Nullable String str) { 509 if (DEBUG) { 510 Slog.d(TAG, "putEnabledInputMethodStr: " + str); 511 } 512 if (TextUtils.isEmpty(str)) { 513 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the 514 // empty data scenario. 515 putString(Settings.Secure.ENABLED_INPUT_METHODS, null); 516 } else { 517 putString(Settings.Secure.ENABLED_INPUT_METHODS, str); 518 } 519 // TODO: Update callers of putEnabledInputMethodsStr to make str @NonNull. 520 mEnabledInputMethodsStrCache = (str != null ? str : ""); 521 } 522 523 @NonNull getEnabledInputMethodsStr()524 String getEnabledInputMethodsStr() { 525 mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, ""); 526 if (DEBUG) { 527 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache 528 + ", " + mCurrentUserId); 529 } 530 return mEnabledInputMethodsStrCache; 531 } 532 saveSubtypeHistory( List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId)533 private void saveSubtypeHistory( 534 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 535 StringBuilder builder = new StringBuilder(); 536 boolean isImeAdded = false; 537 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 538 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append( 539 newSubtypeId); 540 isImeAdded = true; 541 } 542 for (Pair<String, String> ime: savedImes) { 543 String imeId = ime.first; 544 String subtypeId = ime.second; 545 if (TextUtils.isEmpty(subtypeId)) { 546 subtypeId = NOT_A_SUBTYPE_ID_STR; 547 } 548 if (isImeAdded) { 549 builder.append(INPUT_METHOD_SEPARATOR); 550 } else { 551 isImeAdded = true; 552 } 553 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append( 554 subtypeId); 555 } 556 // Remove the last INPUT_METHOD_SEPARATOR 557 putSubtypeHistoryStr(builder.toString()); 558 } 559 addSubtypeToHistory(String imeId, String subtypeId)560 private void addSubtypeToHistory(String imeId, String subtypeId) { 561 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 562 for (Pair<String, String> ime: subtypeHistory) { 563 if (ime.first.equals(imeId)) { 564 if (DEBUG) { 565 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 566 + ime.second); 567 } 568 // We should break here 569 subtypeHistory.remove(ime); 570 break; 571 } 572 } 573 if (DEBUG) { 574 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); 575 } 576 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 577 } 578 putSubtypeHistoryStr(@onNull String str)579 private void putSubtypeHistoryStr(@NonNull String str) { 580 if (DEBUG) { 581 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 582 } 583 if (TextUtils.isEmpty(str)) { 584 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty 585 // data scenario. 586 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null); 587 } else { 588 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 589 } 590 } 591 getLastInputMethodAndSubtypeLocked()592 Pair<String, String> getLastInputMethodAndSubtypeLocked() { 593 // Gets the first one from the history 594 return getLastSubtypeForInputMethodLockedInternal(null); 595 } 596 597 @Nullable getLastInputMethodSubtypeLocked()598 InputMethodSubtype getLastInputMethodSubtypeLocked() { 599 final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked(); 600 // TODO: Handle the case of the last IME with no subtypes 601 if (lastIme == null || TextUtils.isEmpty(lastIme.first) 602 || TextUtils.isEmpty(lastIme.second)) return null; 603 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); 604 if (lastImi == null) return null; 605 try { 606 final int lastSubtypeHash = Integer.parseInt(lastIme.second); 607 final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, 608 lastSubtypeHash); 609 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { 610 return null; 611 } 612 return lastImi.getSubtypeAt(lastSubtypeId); 613 } catch (NumberFormatException e) { 614 return null; 615 } 616 } 617 getLastSubtypeForInputMethodLocked(String imeId)618 String getLastSubtypeForInputMethodLocked(String imeId) { 619 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 620 if (ime != null) { 621 return ime.second; 622 } else { 623 return null; 624 } 625 } 626 getLastSubtypeForInputMethodLockedInternal(String imeId)627 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 628 List<Pair<String, ArrayList<String>>> enabledImes = 629 getEnabledInputMethodsAndSubtypeListLocked(); 630 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 631 for (Pair<String, String> imeAndSubtype : subtypeHistory) { 632 final String imeInTheHistory = imeAndSubtype.first; 633 // If imeId is empty, returns the first IME and subtype in the history 634 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 635 final String subtypeInTheHistory = imeAndSubtype.second; 636 final String subtypeHashCode = 637 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( 638 enabledImes, imeInTheHistory, subtypeInTheHistory); 639 if (!TextUtils.isEmpty(subtypeHashCode)) { 640 if (DEBUG) { 641 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); 642 } 643 return new Pair<>(imeInTheHistory, subtypeHashCode); 644 } 645 } 646 } 647 if (DEBUG) { 648 Slog.d(TAG, "No enabled IME found in the history"); 649 } 650 return null; 651 } 652 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode)653 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, 654 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 655 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 656 if (enabledIme.first.equals(imeId)) { 657 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 658 final InputMethodInfo imi = mMethodMap.get(imeId); 659 if (explicitlyEnabledSubtypes.size() == 0) { 660 // If there are no explicitly enabled subtypes, applicable subtypes are 661 // enabled implicitly. 662 // If IME is enabled and no subtypes are enabled, applicable subtypes 663 // are enabled implicitly, so needs to treat them to be enabled. 664 if (imi != null && imi.getSubtypeCount() > 0) { 665 List<InputMethodSubtype> implicitlyEnabledSubtypes = 666 SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi); 667 final int numSubtypes = implicitlyEnabledSubtypes.size(); 668 for (int i = 0; i < numSubtypes; ++i) { 669 final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i); 670 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 671 return subtypeHashCode; 672 } 673 } 674 } 675 } else { 676 for (String s: explicitlyEnabledSubtypes) { 677 if (s.equals(subtypeHashCode)) { 678 // If both imeId and subtypeId are enabled, return subtypeId. 679 try { 680 final int hashCode = Integer.parseInt(subtypeHashCode); 681 // Check whether the subtype id is valid or not 682 if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) { 683 return s; 684 } else { 685 return NOT_A_SUBTYPE_ID_STR; 686 } 687 } catch (NumberFormatException e) { 688 return NOT_A_SUBTYPE_ID_STR; 689 } 690 } 691 } 692 } 693 // If imeId was enabled but subtypeId was disabled. 694 return NOT_A_SUBTYPE_ID_STR; 695 } 696 } 697 // If both imeId and subtypeId are disabled, return null 698 return null; 699 } 700 loadInputMethodAndSubtypeHistoryLocked()701 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 702 ArrayList<Pair<String, String>> imsList = new ArrayList<>(); 703 final String subtypeHistoryStr = getSubtypeHistoryStr(); 704 if (TextUtils.isEmpty(subtypeHistoryStr)) { 705 return imsList; 706 } 707 mInputMethodSplitter.setString(subtypeHistoryStr); 708 while (mInputMethodSplitter.hasNext()) { 709 String nextImsStr = mInputMethodSplitter.next(); 710 mSubtypeSplitter.setString(nextImsStr); 711 if (mSubtypeSplitter.hasNext()) { 712 String subtypeId = NOT_A_SUBTYPE_ID_STR; 713 // The first element is ime id. 714 String imeId = mSubtypeSplitter.next(); 715 while (mSubtypeSplitter.hasNext()) { 716 subtypeId = mSubtypeSplitter.next(); 717 break; 718 } 719 imsList.add(new Pair<>(imeId, subtypeId)); 720 } 721 } 722 return imsList; 723 } 724 725 @NonNull getSubtypeHistoryStr()726 private String getSubtypeHistoryStr() { 727 final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, ""); 728 if (DEBUG) { 729 Slog.d(TAG, "getSubtypeHistoryStr: " + history); 730 } 731 return history; 732 } 733 putSelectedInputMethod(String imeId)734 void putSelectedInputMethod(String imeId) { 735 if (DEBUG) { 736 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " 737 + mCurrentUserId); 738 } 739 putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 740 } 741 putSelectedSubtype(int subtypeId)742 void putSelectedSubtype(int subtypeId) { 743 if (DEBUG) { 744 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " 745 + mCurrentUserId); 746 } 747 putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 748 } 749 750 @Nullable getSelectedInputMethod()751 String getSelectedInputMethod() { 752 final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null); 753 if (DEBUG) { 754 Slog.d(TAG, "getSelectedInputMethodStr: " + imi); 755 } 756 return imi; 757 } 758 759 @Nullable getSelectedInputMethodForUser(@serIdInt int userId)760 String getSelectedInputMethodForUser(@UserIdInt int userId) { 761 final String imi = 762 getStringForUser(Settings.Secure.DEFAULT_INPUT_METHOD, null, userId); 763 if (DEBUG) { 764 Slog.d(TAG, "getSelectedInputMethodForUserStr: " + imi); 765 } 766 return imi; 767 } 768 putDefaultVoiceInputMethod(String imeId)769 void putDefaultVoiceInputMethod(String imeId) { 770 if (DEBUG) { 771 Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId); 772 } 773 putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId); 774 } 775 776 @Nullable getDefaultVoiceInputMethod()777 String getDefaultVoiceInputMethod() { 778 final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null); 779 if (DEBUG) { 780 Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi); 781 } 782 return imi; 783 } 784 isSubtypeSelected()785 boolean isSubtypeSelected() { 786 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; 787 } 788 getSelectedInputMethodSubtypeHashCode()789 private int getSelectedInputMethodSubtypeHashCode() { 790 return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID); 791 } 792 isShowImeWithHardKeyboardEnabled()793 boolean isShowImeWithHardKeyboardEnabled() { 794 return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false); 795 } 796 setShowImeWithHardKeyboard(boolean show)797 void setShowImeWithHardKeyboard(boolean show) { 798 putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show); 799 } 800 801 @UserIdInt getCurrentUserId()802 public int getCurrentUserId() { 803 return mCurrentUserId; 804 } 805 getSelectedInputMethodSubtypeId(String selectedImiId)806 int getSelectedInputMethodSubtypeId(String selectedImiId) { 807 final InputMethodInfo imi = mMethodMap.get(selectedImiId); 808 if (imi == null) { 809 return NOT_A_SUBTYPE_ID; 810 } 811 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 812 return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode); 813 } 814 saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, InputMethodSubtype currentSubtype)815 void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, 816 InputMethodSubtype currentSubtype) { 817 String subtypeId = NOT_A_SUBTYPE_ID_STR; 818 if (currentSubtype != null) { 819 subtypeId = String.valueOf(currentSubtype.hashCode()); 820 } 821 if (canAddToLastInputMethod(currentSubtype)) { 822 addSubtypeToHistory(curMethodId, subtypeId); 823 } 824 } 825 826 /** 827 * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for 828 * non-current users. 829 * 830 * <p>TODO: Address code duplication between this and 831 * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p> 832 * 833 * @return {@link InputMethodSubtype} if exists. {@code null} otherwise. 834 */ 835 @Nullable getCurrentInputMethodSubtypeForNonCurrentUsers()836 InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() { 837 final String selectedMethodId = getSelectedInputMethod(); 838 if (selectedMethodId == null) { 839 return null; 840 } 841 final InputMethodInfo imi = mMethodMap.get(selectedMethodId); 842 if (imi == null || imi.getSubtypeCount() == 0) { 843 return null; 844 } 845 846 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 847 if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) { 848 final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi, 849 subtypeHashCode); 850 if (subtypeIndex >= 0) { 851 return imi.getSubtypeAt(subtypeIndex); 852 } 853 } 854 855 // If there are no selected subtypes, the framework will try to find the most applicable 856 // subtype from explicitly or implicitly enabled subtypes. 857 final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 858 getEnabledInputMethodSubtypeListLocked(imi, true); 859 // If there is only one explicitly or implicitly enabled subtype, just returns it. 860 if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) { 861 return null; 862 } 863 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 864 return explicitlyOrImplicitlyEnabledSubtypes.get(0); 865 } 866 final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( 867 mRes, explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, 868 null, true); 869 if (subtype != null) { 870 return subtype; 871 } 872 return SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes, 873 explicitlyOrImplicitlyEnabledSubtypes, null, null, true); 874 } 875 setAdditionalInputMethodSubtypes(@onNull String imeId, @NonNull ArrayList<InputMethodSubtype> subtypes, @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, @NonNull PackageManagerInternal packageManagerInternal, int callingUid)876 boolean setAdditionalInputMethodSubtypes(@NonNull String imeId, 877 @NonNull ArrayList<InputMethodSubtype> subtypes, 878 @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, 879 @NonNull PackageManagerInternal packageManagerInternal, int callingUid) { 880 final InputMethodInfo imi = mMethodMap.get(imeId); 881 if (imi == null) { 882 return false; 883 } 884 if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid, 885 imi.getPackageName())) { 886 return false; 887 } 888 889 if (subtypes.isEmpty()) { 890 additionalSubtypeMap.remove(imi.getId()); 891 } else { 892 additionalSubtypeMap.put(imi.getId(), subtypes); 893 } 894 AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId()); 895 return true; 896 } 897 setEnabledInputMethodSubtypes(@onNull String imeId, @NonNull int[] subtypeHashCodes)898 boolean setEnabledInputMethodSubtypes(@NonNull String imeId, 899 @NonNull int[] subtypeHashCodes) { 900 final InputMethodInfo imi = mMethodMap.get(imeId); 901 if (imi == null) { 902 return false; 903 } 904 905 final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length); 906 for (int subtypeHashCode : subtypeHashCodes) { 907 if (subtypeHashCode == NOT_A_SUBTYPE_ID) { 908 continue; // NOT_A_SUBTYPE_ID must not be saved 909 } 910 if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) { 911 continue; // this subtype does not exist in InputMethodInfo. 912 } 913 if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) { 914 continue; // The entry is already added. No need to add anymore. 915 } 916 validSubtypeHashCodes.add(subtypeHashCode); 917 } 918 919 final String originalEnabledImesString = getEnabledInputMethodsStr(); 920 final String updatedEnabledImesString = updateEnabledImeString( 921 originalEnabledImesString, imi.getId(), validSubtypeHashCodes); 922 if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) { 923 return false; 924 } 925 926 putEnabledInputMethodsStr(updatedEnabledImesString); 927 return true; 928 } 929 930 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) updateEnabledImeString(@onNull String enabledImesString, @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes)931 static String updateEnabledImeString(@NonNull String enabledImesString, 932 @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) { 933 final TextUtils.SimpleStringSplitter imeSplitter = 934 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); 935 final TextUtils.SimpleStringSplitter imeSubtypeSplitter = 936 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); 937 938 final StringBuilder sb = new StringBuilder(); 939 940 imeSplitter.setString(enabledImesString); 941 boolean needsImeSeparator = false; 942 while (imeSplitter.hasNext()) { 943 final String nextImsStr = imeSplitter.next(); 944 imeSubtypeSplitter.setString(nextImsStr); 945 if (imeSubtypeSplitter.hasNext()) { 946 if (needsImeSeparator) { 947 sb.append(INPUT_METHOD_SEPARATOR); 948 } 949 if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) { 950 sb.append(imeId); 951 for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) { 952 sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR); 953 sb.append(enabledSubtypeHashCodes.get(i)); 954 } 955 } else { 956 sb.append(nextImsStr); 957 } 958 needsImeSeparator = true; 959 } 960 } 961 return sb.toString(); 962 } 963 dumpLocked(final Printer pw, final String prefix)964 public void dumpLocked(final Printer pw, final String prefix) { 965 pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); 966 pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds)); 967 pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite); 968 pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache); 969 } 970 } 971 isSoftInputModeStateVisibleAllowed(int targetSdkVersion, @StartInputFlags int startInputFlags)972 static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion, 973 @StartInputFlags int startInputFlags) { 974 if (targetSdkVersion < Build.VERSION_CODES.P) { 975 // for compatibility. 976 return true; 977 } 978 if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) { 979 return false; 980 } 981 if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) { 982 return false; 983 } 984 return true; 985 } 986 987 /** 988 * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a 989 * list of real user IDs. 990 * 991 * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and 992 * {@link UserHandle#USER_ALL} are also supported 993 * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT} 994 * is specified in {@code userIdToBeResolved}. 995 * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if 996 * no debug message is required. 997 * @return An integer array that contain user IDs. 998 */ resolveUserId(@serIdInt int userIdToBeResolved, @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter)999 static int[] resolveUserId(@UserIdInt int userIdToBeResolved, 1000 @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) { 1001 final UserManagerInternal userManagerInternal = 1002 LocalServices.getService(UserManagerInternal.class); 1003 1004 if (userIdToBeResolved == UserHandle.USER_ALL) { 1005 return userManagerInternal.getUserIds(); 1006 } 1007 1008 final int sourceUserId; 1009 if (userIdToBeResolved == UserHandle.USER_CURRENT) { 1010 sourceUserId = currentUserId; 1011 } else if (userIdToBeResolved < 0) { 1012 if (warningWriter != null) { 1013 warningWriter.print("Pseudo user ID "); 1014 warningWriter.print(userIdToBeResolved); 1015 warningWriter.println(" is not supported."); 1016 } 1017 return new int[]{}; 1018 } else if (userManagerInternal.exists(userIdToBeResolved)) { 1019 sourceUserId = userIdToBeResolved; 1020 } else { 1021 if (warningWriter != null) { 1022 warningWriter.print("User #"); 1023 warningWriter.print(userIdToBeResolved); 1024 warningWriter.println(" does not exit."); 1025 } 1026 return new int[]{}; 1027 } 1028 return new int[]{sourceUserId}; 1029 } 1030 1031 /** 1032 * Convert the input method ID to a component name 1033 * 1034 * @param id A unique ID for this input method. 1035 * @return The component name of the input method. 1036 * @see InputMethodInfo#computeId(ResolveInfo) 1037 */ 1038 @Nullable convertIdToComponentName(@onNull String id)1039 public static ComponentName convertIdToComponentName(@NonNull String id) { 1040 return ComponentName.unflattenFromString(id); 1041 } 1042 } 1043