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