• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale)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          */
parseLanguageFromLocaleString(final String locale)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
compareTo(ImeSubtypeListItem other)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
toString()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
equals(Object o)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 
InputMethodAndSubtypeList(Context context, InputMethodSettings settings)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 
getSortedInputMethodAndSubtypeList( boolean includeAuxiliarySubtypes, boolean isScreenLocked)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 
calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype)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;
StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList)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          */
getIndex(InputMethodInfo imi, InputMethodSubtype subtype)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
getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean forward)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 
dump(final Printer pw, final String prefix)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 
DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems)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          */
getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype)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 
onUserAction(InputMethodInfo imi, InputMethodSubtype subtype)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
getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean forward)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 
dump(final Printer pw, final String prefix)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 
createFrom(final ControllerImpl currentInstance, final List<ImeSubtypeListItem> sortedEnabledItems)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 
ControllerImpl(final DynamicRotationList switchingAwareRotationList, final StaticRotationList switchingUnawareRotationList)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
getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean forward)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 
onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype)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 
filterImeSubtypeList( final List<ImeSubtypeListItem> items, final boolean supportsSwitchingToNextInputMethod)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 
dump(final Printer pw)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 
InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context)552     private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
553         mSettings = settings;
554         resetCircularListLocked(context);
555     }
556 
createInstanceLocked( InputMethodSettings settings, Context context)557     public static InputMethodSubtypeSwitchingController createInstanceLocked(
558             InputMethodSettings settings, Context context) {
559         return new InputMethodSubtypeSwitchingController(settings, context);
560     }
561 
onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype)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 
resetCircularListLocked(Context context)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 
getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype, boolean forward)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 
getSortedInputMethodAndSubtypeListLocked( boolean includingAuxiliarySubtypes, boolean isScreenLocked)590     public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(
591             boolean includingAuxiliarySubtypes, boolean isScreenLocked) {
592         return mSubtypeList.getSortedInputMethodAndSubtypeList(
593                 includingAuxiliarySubtypes, isScreenLocked);
594     }
595 
dump(final Printer pw)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