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