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