Home | History | Annotate | Download | only in inputmethod
      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