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