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