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.AnyThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.UserIdInt; 23 import android.content.Context; 24 import android.os.UserHandle; 25 import android.text.TextUtils; 26 import android.util.ArraySet; 27 import android.util.Printer; 28 import android.util.Slog; 29 import android.view.inputmethod.InputMethodInfo; 30 import android.view.inputmethod.InputMethodSubtype; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 import java.util.Objects; 38 39 /** 40 * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes. 41 * 42 * <p>This class is designed to be used from and only from {@link InputMethodManagerService} by 43 * using {@link ImfLock ImfLock.class} as a global lock.</p> 44 */ 45 final class InputMethodSubtypeSwitchingController { 46 private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName(); 47 private static final boolean DEBUG = false; 48 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; 49 50 public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> { 51 public final CharSequence mImeName; 52 public final CharSequence mSubtypeName; 53 public final InputMethodInfo mImi; 54 public final int mSubtypeId; 55 public final boolean mIsSystemLocale; 56 public final boolean mIsSystemLanguage; 57 ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale)58 ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, 59 InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { 60 mImeName = imeName; 61 mSubtypeName = subtypeName; 62 mImi = imi; 63 mSubtypeId = subtypeId; 64 if (TextUtils.isEmpty(subtypeLocale)) { 65 mIsSystemLocale = false; 66 mIsSystemLanguage = false; 67 } else { 68 mIsSystemLocale = subtypeLocale.equals(systemLocale); 69 if (mIsSystemLocale) { 70 mIsSystemLanguage = true; 71 } else { 72 // TODO: Use Locale#getLanguage or Locale#toLanguageTag 73 final String systemLanguage = LocaleUtils.getLanguageFromLocaleString( 74 systemLocale); 75 final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString( 76 subtypeLocale); 77 mIsSystemLanguage = systemLanguage.length() >= 2 78 && systemLanguage.equals(subtypeLanguage); 79 } 80 } 81 } 82 compareNullableCharSequences(@ullable CharSequence c1, @Nullable CharSequence c2)83 private static int compareNullableCharSequences(@Nullable CharSequence c1, 84 @Nullable CharSequence c2) { 85 // For historical reasons, an empty text needs to put at the last. 86 final boolean empty1 = TextUtils.isEmpty(c1); 87 final boolean empty2 = TextUtils.isEmpty(c2); 88 if (empty1 || empty2) { 89 return (empty1 ? 1 : 0) - (empty2 ? 1 : 0); 90 } 91 return c1.toString().compareTo(c2.toString()); 92 } 93 94 /** 95 * Compares this object with the specified object for order. The fields of this class will 96 * be compared in the following order. 97 * <ol> 98 * <li>{@link #mImeName}</li> 99 * <li>{@link #mIsSystemLocale}</li> 100 * <li>{@link #mIsSystemLanguage}</li> 101 * <li>{@link #mSubtypeName}</li> 102 * <li>{@link #mImi} with {@link InputMethodInfo#getId()}</li> 103 * </ol> 104 * Note: this class has a natural ordering that is inconsistent with {@link #equals(Object). 105 * This method doesn't compare {@link #mSubtypeId} but {@link #equals(Object)} does. 106 * 107 * @param other the object to be compared. 108 * @return a negative integer, zero, or positive integer as this object is less than, equal 109 * to, or greater than the specified <code>other</code> object. 110 */ 111 @Override compareTo(ImeSubtypeListItem other)112 public int compareTo(ImeSubtypeListItem other) { 113 int result = compareNullableCharSequences(mImeName, other.mImeName); 114 if (result != 0) { 115 return result; 116 } 117 // Subtype that has the same locale of the system's has higher priority. 118 result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0); 119 if (result != 0) { 120 return result; 121 } 122 // Subtype that has the same language of the system's has higher priority. 123 result = (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0); 124 if (result != 0) { 125 return result; 126 } 127 result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName); 128 if (result != 0) { 129 return result; 130 } 131 return mImi.getId().compareTo(other.mImi.getId()); 132 } 133 134 @Override toString()135 public String toString() { 136 return "ImeSubtypeListItem{" 137 + "mImeName=" + mImeName 138 + " mSubtypeName=" + mSubtypeName 139 + " mSubtypeId=" + mSubtypeId 140 + " mIsSystemLocale=" + mIsSystemLocale 141 + " mIsSystemLanguage=" + mIsSystemLanguage 142 + "}"; 143 } 144 145 @Override equals(Object o)146 public boolean equals(Object o) { 147 if (o == this) { 148 return true; 149 } 150 if (o instanceof ImeSubtypeListItem) { 151 final ImeSubtypeListItem that = (ImeSubtypeListItem) o; 152 return Objects.equals(this.mImi, that.mImi) && this.mSubtypeId == that.mSubtypeId; 153 } 154 return false; 155 } 156 } 157 getSortedInputMethodAndSubtypeList( boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu, @NonNull Context context, @NonNull InputMethodMap methodMap, @UserIdInt int userId)158 static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( 159 boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu, 160 @NonNull Context context, @NonNull InputMethodMap methodMap, 161 @UserIdInt int userId) { 162 final Context userAwareContext = context.getUserId() == userId 163 ? context 164 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); 165 final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag(); 166 final InputMethodSettings settings = InputMethodSettings.create(methodMap, userId); 167 168 final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList(); 169 if (imis.isEmpty()) { 170 Slog.w(TAG, "Enabled input method list is empty."); 171 return new ArrayList<>(); 172 } 173 if (isScreenLocked && includeAuxiliarySubtypes) { 174 if (DEBUG) { 175 Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen."); 176 } 177 includeAuxiliarySubtypes = false; 178 } 179 final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>(); 180 final int numImes = imis.size(); 181 for (int i = 0; i < numImes; ++i) { 182 final InputMethodInfo imi = imis.get(i); 183 if (forImeMenu && !imi.shouldShowInInputMethodPicker()) { 184 continue; 185 } 186 final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = 187 settings.getEnabledInputMethodSubtypeList(imi, true); 188 final ArraySet<String> enabledSubtypeSet = new ArraySet<>(); 189 for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) { 190 enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); 191 } 192 final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager()); 193 if (enabledSubtypeSet.size() > 0) { 194 final int subtypeCount = imi.getSubtypeCount(); 195 if (DEBUG) { 196 Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); 197 } 198 for (int j = 0; j < subtypeCount; ++j) { 199 final InputMethodSubtype subtype = imi.getSubtypeAt(j); 200 final String subtypeHashCode = String.valueOf(subtype.hashCode()); 201 // We show all enabled IMEs and subtypes when an IME is shown. 202 if (enabledSubtypeSet.contains(subtypeHashCode) 203 && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) { 204 final CharSequence subtypeLabel = 205 subtype.overridesImplicitlyEnabledSubtype() ? null : subtype 206 .getDisplayName(userAwareContext, imi.getPackageName(), 207 imi.getServiceInfo().applicationInfo); 208 imList.add(new ImeSubtypeListItem(imeLabel, 209 subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); 210 211 // Removing this subtype from enabledSubtypeSet because we no 212 // longer need to add an entry of this subtype to imList to avoid 213 // duplicated entries. 214 enabledSubtypeSet.remove(subtypeHashCode); 215 } 216 } 217 } else { 218 imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null, 219 mSystemLocaleStr)); 220 } 221 } 222 Collections.sort(imList); 223 return imList; 224 } 225 calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype)226 private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { 227 return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) 228 : NOT_A_SUBTYPE_ID; 229 } 230 231 private static class StaticRotationList { 232 private final List<ImeSubtypeListItem> mImeSubtypeList; StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList)233 StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { 234 mImeSubtypeList = imeSubtypeList; 235 } 236 237 /** 238 * Returns the index of the specified input method and subtype in the given list. 239 * 240 * @param imi The {@link InputMethodInfo} to be searched. 241 * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method 242 * does not have a subtype. 243 * @return The index in the given list. -1 if not found. 244 */ getIndex(InputMethodInfo imi, InputMethodSubtype subtype)245 private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { 246 final int currentSubtypeId = calculateSubtypeId(imi, subtype); 247 final int numSubtypes = mImeSubtypeList.size(); 248 for (int i = 0; i < numSubtypes; ++i) { 249 final ImeSubtypeListItem isli = mImeSubtypeList.get(i); 250 // Skip until the current IME/subtype is found. 251 if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { 252 return i; 253 } 254 } 255 return -1; 256 } 257 getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype)258 public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, 259 InputMethodInfo imi, InputMethodSubtype subtype) { 260 if (imi == null) { 261 return null; 262 } 263 if (mImeSubtypeList.size() <= 1) { 264 return null; 265 } 266 final int currentIndex = getIndex(imi, subtype); 267 if (currentIndex < 0) { 268 return null; 269 } 270 final int numSubtypes = mImeSubtypeList.size(); 271 for (int offset = 1; offset < numSubtypes; ++offset) { 272 // Start searching the next IME/subtype from the next of the current index. 273 final int candidateIndex = (currentIndex + offset) % numSubtypes; 274 final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); 275 // Skip if searching inside the current IME only, but the candidate is not 276 // the current IME. 277 if (onlyCurrentIme && !imi.equals(candidate.mImi)) { 278 continue; 279 } 280 return candidate; 281 } 282 return null; 283 } 284 dump(final Printer pw, final String prefix)285 protected void dump(final Printer pw, final String prefix) { 286 final int numSubtypes = mImeSubtypeList.size(); 287 for (int i = 0; i < numSubtypes; ++i) { 288 final int rank = i; 289 final ImeSubtypeListItem item = mImeSubtypeList.get(i); 290 pw.println(prefix + "rank=" + rank + " item=" + item); 291 } 292 } 293 } 294 295 private static class DynamicRotationList { 296 private static final String TAG = DynamicRotationList.class.getSimpleName(); 297 private final List<ImeSubtypeListItem> mImeSubtypeList; 298 private final int[] mUsageHistoryOfSubtypeListItemIndex; 299 DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems)300 private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { 301 mImeSubtypeList = imeSubtypeListItems; 302 mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; 303 final int numSubtypes = mImeSubtypeList.size(); 304 for (int i = 0; i < numSubtypes; i++) { 305 mUsageHistoryOfSubtypeListItemIndex[i] = i; 306 } 307 } 308 309 /** 310 * Returns the index of the specified object in 311 * {@link #mUsageHistoryOfSubtypeListItemIndex}. 312 * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank" 313 * so as not to be confused with the index in {@link #mImeSubtypeList}. 314 * 315 * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. 316 */ getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype)317 private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { 318 final int currentSubtypeId = calculateSubtypeId(imi, subtype); 319 final int numItems = mUsageHistoryOfSubtypeListItemIndex.length; 320 for (int usageRank = 0; usageRank < numItems; usageRank++) { 321 final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank]; 322 final ImeSubtypeListItem subtypeListItem = 323 mImeSubtypeList.get(subtypeListItemIndex); 324 if (subtypeListItem.mImi.equals(imi) 325 && subtypeListItem.mSubtypeId == currentSubtypeId) { 326 return usageRank; 327 } 328 } 329 // Not found in the known IME/Subtype list. 330 return -1; 331 } 332 onUserAction(InputMethodInfo imi, InputMethodSubtype subtype)333 public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) { 334 final int currentUsageRank = getUsageRank(imi, subtype); 335 // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0 336 if (currentUsageRank <= 0) { 337 return; 338 } 339 final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank]; 340 System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0, 341 mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank); 342 mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; 343 } 344 getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype)345 public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, 346 InputMethodInfo imi, InputMethodSubtype subtype) { 347 int currentUsageRank = getUsageRank(imi, subtype); 348 if (currentUsageRank < 0) { 349 if (DEBUG) { 350 Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype); 351 } 352 return null; 353 } 354 final int numItems = mUsageHistoryOfSubtypeListItemIndex.length; 355 for (int i = 1; i < numItems; i++) { 356 final int subtypeListItemRank = (currentUsageRank + i) % numItems; 357 final int subtypeListItemIndex = 358 mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank]; 359 final ImeSubtypeListItem subtypeListItem = 360 mImeSubtypeList.get(subtypeListItemIndex); 361 if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) { 362 continue; 363 } 364 return subtypeListItem; 365 } 366 return null; 367 } 368 dump(final Printer pw, final String prefix)369 protected void dump(final Printer pw, final String prefix) { 370 for (int rank = 0; rank < mUsageHistoryOfSubtypeListItemIndex.length; ++rank) { 371 final int index = mUsageHistoryOfSubtypeListItemIndex[rank]; 372 final ImeSubtypeListItem item = mImeSubtypeList.get(index); 373 pw.println(prefix + "rank=" + rank + " item=" + item); 374 } 375 } 376 } 377 378 @VisibleForTesting 379 public static class ControllerImpl { 380 private final DynamicRotationList mSwitchingAwareRotationList; 381 private final StaticRotationList mSwitchingUnawareRotationList; 382 createFrom(final ControllerImpl currentInstance, final List<ImeSubtypeListItem> sortedEnabledItems)383 public static ControllerImpl createFrom(final ControllerImpl currentInstance, 384 final List<ImeSubtypeListItem> sortedEnabledItems) { 385 DynamicRotationList switchingAwareRotationList = null; 386 { 387 final List<ImeSubtypeListItem> switchingAwareImeSubtypes = 388 filterImeSubtypeList(sortedEnabledItems, 389 true /* supportsSwitchingToNextInputMethod */); 390 if (currentInstance != null 391 && currentInstance.mSwitchingAwareRotationList != null 392 && Objects.equals( 393 currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, 394 switchingAwareImeSubtypes)) { 395 // Can reuse the current instance. 396 switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; 397 } 398 if (switchingAwareRotationList == null) { 399 switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); 400 } 401 } 402 403 StaticRotationList switchingUnawareRotationList = null; 404 { 405 final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( 406 sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); 407 if (currentInstance != null 408 && currentInstance.mSwitchingUnawareRotationList != null 409 && Objects.equals( 410 currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, 411 switchingUnawareImeSubtypes)) { 412 // Can reuse the current instance. 413 switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; 414 } 415 if (switchingUnawareRotationList == null) { 416 switchingUnawareRotationList = 417 new StaticRotationList(switchingUnawareImeSubtypes); 418 } 419 } 420 421 return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); 422 } 423 ControllerImpl(final DynamicRotationList switchingAwareRotationList, final StaticRotationList switchingUnawareRotationList)424 private ControllerImpl(final DynamicRotationList switchingAwareRotationList, 425 final StaticRotationList switchingUnawareRotationList) { 426 mSwitchingAwareRotationList = switchingAwareRotationList; 427 mSwitchingUnawareRotationList = switchingUnawareRotationList; 428 } 429 getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype)430 public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, 431 InputMethodSubtype subtype) { 432 if (imi == null) { 433 return null; 434 } 435 if (imi.supportsSwitchingToNextInputMethod()) { 436 return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, 437 subtype); 438 } else { 439 return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, 440 subtype); 441 } 442 } 443 onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype)444 public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { 445 if (imi == null) { 446 return; 447 } 448 if (imi.supportsSwitchingToNextInputMethod()) { 449 mSwitchingAwareRotationList.onUserAction(imi, subtype); 450 } 451 } 452 filterImeSubtypeList( final List<ImeSubtypeListItem> items, final boolean supportsSwitchingToNextInputMethod)453 private static List<ImeSubtypeListItem> filterImeSubtypeList( 454 final List<ImeSubtypeListItem> items, 455 final boolean supportsSwitchingToNextInputMethod) { 456 final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); 457 final int numItems = items.size(); 458 for (int i = 0; i < numItems; i++) { 459 final ImeSubtypeListItem item = items.get(i); 460 if (item.mImi.supportsSwitchingToNextInputMethod() 461 == supportsSwitchingToNextInputMethod) { 462 result.add(item); 463 } 464 } 465 return result; 466 } 467 dump(@onNull Printer pw, @NonNull String prefix)468 protected void dump(@NonNull Printer pw, @NonNull String prefix) { 469 pw.println(prefix + "mSwitchingAwareRotationList:"); 470 mSwitchingAwareRotationList.dump(pw, prefix + " "); 471 pw.println(prefix + "mSwitchingUnawareRotationList:"); 472 mSwitchingUnawareRotationList.dump(pw, prefix + " "); 473 } 474 } 475 476 private final Context mContext; 477 @UserIdInt 478 private final int mUserId; 479 private ControllerImpl mController; 480 InputMethodSubtypeSwitchingController(@onNull Context context, @NonNull InputMethodMap methodMap, @UserIdInt int userId)481 private InputMethodSubtypeSwitchingController(@NonNull Context context, 482 @NonNull InputMethodMap methodMap, @UserIdInt int userId) { 483 mContext = context; 484 mUserId = userId; 485 mController = ControllerImpl.createFrom(null, 486 getSortedInputMethodAndSubtypeList( 487 false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, 488 false /* forImeMenu */, context, methodMap, userId)); 489 } 490 491 @NonNull createInstanceLocked( @onNull Context context, @NonNull InputMethodMap methodMap, @UserIdInt int userId)492 public static InputMethodSubtypeSwitchingController createInstanceLocked( 493 @NonNull Context context, 494 @NonNull InputMethodMap methodMap, @UserIdInt int userId) { 495 return new InputMethodSubtypeSwitchingController(context, methodMap, userId); 496 } 497 498 @AnyThread 499 @UserIdInt getUserId()500 int getUserId() { 501 return mUserId; 502 } 503 onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype)504 public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { 505 if (mController == null) { 506 if (DEBUG) { 507 Slog.e(TAG, "mController shouldn't be null."); 508 } 509 return; 510 } 511 mController.onUserActionLocked(imi, subtype); 512 } 513 resetCircularListLocked(@onNull InputMethodMap methodMap)514 public void resetCircularListLocked(@NonNull InputMethodMap methodMap) { 515 mController = ControllerImpl.createFrom(mController, 516 getSortedInputMethodAndSubtypeList( 517 false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, 518 false /* forImeMenu */, mContext, methodMap, mUserId)); 519 } 520 getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype)521 public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, 522 InputMethodSubtype subtype) { 523 if (mController == null) { 524 if (DEBUG) { 525 Slog.e(TAG, "mController shouldn't be null."); 526 } 527 return null; 528 } 529 return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); 530 } 531 dump(@onNull Printer pw, @NonNull String prefix)532 public void dump(@NonNull Printer pw, @NonNull String prefix) { 533 if (mController != null) { 534 mController.dump(pw, prefix); 535 } else { 536 pw.println(prefix + "mController=null"); 537 } 538 } 539 } 540