blob: b13c307497d92929ba382b3d3953ae7abc11a51a [file] [log] [blame]
Satoshi Kataokad7443c82013-10-15 17:45:43 +09001/*
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
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070017package com.android.server.inputmethod;
Satoshi Kataokad7443c82013-10-15 17:45:43 +090018
Yohei Yukawab1e2f4f2016-04-11 13:13:57 -070019import android.annotation.Nullable;
Satoshi Kataokad787f692013-10-26 04:44:21 +090020import android.content.Context;
21import android.content.pm.PackageManager;
22import android.text.TextUtils;
Yohei Yukawa9b89a0b2018-11-20 18:45:59 -080023import android.util.ArraySet;
Yohei Yukawa9b29d042014-05-22 20:26:39 +090024import android.util.Log;
Yohei Yukawad7248862015-06-03 23:56:12 -070025import android.util.Printer;
Satoshi Kataokad7443c82013-10-15 17:45:43 +090026import android.util.Slog;
27import android.view.inputmethod.InputMethodInfo;
28import android.view.inputmethod.InputMethodSubtype;
29
Yohei Yukawa40139402014-05-21 22:56:40 +090030import com.android.internal.annotations.VisibleForTesting;
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070031import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
Yohei Yukawa40139402014-05-21 22:56:40 +090032
Satoshi Kataokad787f692013-10-26 04:44:21 +090033import java.util.ArrayList;
34import java.util.Collections;
Satoshi Kataokad787f692013-10-26 04:44:21 +090035import java.util.List;
36import java.util.Locale;
Yohei Yukawa07bd7322014-06-02 15:32:59 +090037import java.util.Objects;
Satoshi Kataokad7443c82013-10-15 17:45:43 +090038
39/**
40 * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes.
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070041 *
42 * <p>This class is designed to be used from and only from {@link InputMethodManagerService} by
43 * using {@link InputMethodManagerService#mMethodMap} as a global lock.</p>
Satoshi Kataokad7443c82013-10-15 17:45:43 +090044 */
Yohei Yukawae6b6e0e2018-09-12 16:42:48 -070045final class InputMethodSubtypeSwitchingController {
Satoshi Kataokad7443c82013-10-15 17:45:43 +090046 private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
47 private static final boolean DEBUG = false;
Satoshi Kataokad787f692013-10-26 04:44:21 +090048 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
49
Satoshi Kataokad787f692013-10-26 04:44:21 +090050 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;
Yohei Yukawa71cf0a32014-07-24 09:54:59 +090055 public final boolean mIsSystemLocale;
56 public final boolean mIsSystemLanguage;
Satoshi Kataokad787f692013-10-26 04:44:21 +090057
58 public 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);
Yohei Yukawa71cf0a32014-07-24 09:54:59 +090069 if (mIsSystemLocale) {
70 mIsSystemLanguage = true;
71 } else {
72 // TODO: Use Locale#getLanguage or Locale#toLanguageTag
73 final String systemLanguage = parseLanguageFromLocaleString(systemLocale);
74 final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
75 mIsSystemLanguage = systemLanguage.length() >= 2 &&
76 systemLanguage.equals(subtypeLanguage);
77 }
78 }
79 }
80
81 /**
82 * Returns the language component of a given locale string.
83 * TODO: Use {@link Locale#getLanguage()} instead.
84 */
85 private static String parseLanguageFromLocaleString(final String locale) {
86 final int idx = locale.indexOf('_');
87 if (idx < 0) {
88 return locale;
89 } else {
90 return locale.substring(0, idx);
Satoshi Kataokad787f692013-10-26 04:44:21 +090091 }
92 }
93
Yohei Yukawa410cc502017-01-24 00:29:07 -080094 private static int compareNullableCharSequences(@Nullable CharSequence c1,
95 @Nullable CharSequence c2) {
96 // For historical reasons, an empty text needs to put at the last.
97 final boolean empty1 = TextUtils.isEmpty(c1);
98 final boolean empty2 = TextUtils.isEmpty(c2);
99 if (empty1 || empty2) {
100 return (empty1 ? 1 : 0) - (empty2 ? 1 : 0);
101 }
102 return c1.toString().compareTo(c2.toString());
103 }
104
Tadashi G. Takaoka61dd99b2017-02-08 15:12:13 +0900105 /**
106 * Compares this object with the specified object for order. The fields of this class will
107 * be compared in the following order.
108 * <ol>
109 * <li>{@link #mImeName}</li>
110 * <li>{@link #mIsSystemLocale}</li>
111 * <li>{@link #mIsSystemLanguage}</li>
112 * <li>{@link #mSubtypeName}</li>
Yohei Yukawafbc55472018-11-19 21:54:10 -0800113 * <li>{@link #mImi} with {@link InputMethodInfo#getId()}</li>
Tadashi G. Takaoka61dd99b2017-02-08 15:12:13 +0900114 * </ol>
115 * Note: this class has a natural ordering that is inconsistent with {@link #equals(Object).
116 * This method doesn't compare {@link #mSubtypeId} but {@link #equals(Object)} does.
117 *
118 * @param other the object to be compared.
119 * @return a negative integer, zero, or positive integer as this object is less than, equal
120 * to, or greater than the specified <code>other</code> object.
121 */
Satoshi Kataokad787f692013-10-26 04:44:21 +0900122 @Override
123 public int compareTo(ImeSubtypeListItem other) {
Yohei Yukawa410cc502017-01-24 00:29:07 -0800124 int result = compareNullableCharSequences(mImeName, other.mImeName);
125 if (result != 0) {
126 return result;
Satoshi Kataokad787f692013-10-26 04:44:21 +0900127 }
Tadashi G. Takaoka61dd99b2017-02-08 15:12:13 +0900128 // Subtype that has the same locale of the system's has higher priority.
Yohei Yukawa410cc502017-01-24 00:29:07 -0800129 result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
130 if (result != 0) {
131 return result;
Satoshi Kataokad787f692013-10-26 04:44:21 +0900132 }
Tadashi G. Takaoka61dd99b2017-02-08 15:12:13 +0900133 // Subtype that has the same language of the system's has higher priority.
134 result = (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
135 if (result != 0) {
136 return result;
137 }
Yohei Yukawafbc55472018-11-19 21:54:10 -0800138 result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
139 if (result != 0) {
140 return result;
141 }
142 return mImi.getId().compareTo(other.mImi.getId());
Satoshi Kataokad787f692013-10-26 04:44:21 +0900143 }
Yohei Yukawa1c630792014-05-22 15:55:24 +0900144
145 @Override
146 public String toString() {
147 return "ImeSubtypeListItem{"
148 + "mImeName=" + mImeName
149 + " mSubtypeName=" + mSubtypeName
150 + " mSubtypeId=" + mSubtypeId
151 + " mIsSystemLocale=" + mIsSystemLocale
152 + " mIsSystemLanguage=" + mIsSystemLanguage
153 + "}";
154 }
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900155
156 @Override
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;
Tadashi G. Takaoka61dd99b2017-02-08 15:12:13 +0900163 return Objects.equals(this.mImi, that.mImi) && this.mSubtypeId == that.mSubtypeId;
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900164 }
165 return false;
166 }
Satoshi Kataokad787f692013-10-26 04:44:21 +0900167 }
168
Yohei Yukawad1da1152014-05-01 17:20:05 +0900169 private static class InputMethodAndSubtypeList {
Satoshi Kataokad787f692013-10-26 04:44:21 +0900170 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
Yohei Yukawad1da1152014-05-01 17:20:05 +0900176 public InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
Satoshi Kataokad787f692013-10-26 04:44:21 +0900177 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
Satoshi Kataokad787f692013-10-26 04:44:21 +0900184 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
Yohei Yukawa5f8e7312015-12-10 00:58:55 -0800185 boolean includeAuxiliarySubtypes, boolean isScreenLocked) {
Yohei Yukawa7eee1812018-11-20 17:32:24 -0800186 final ArrayList<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
187 if (imis.isEmpty()) {
Satoshi Kataokad787f692013-10-26 04:44:21 +0900188 return Collections.emptyList();
189 }
Seigo Nonaka14e13912015-05-06 21:04:13 -0700190 if (isScreenLocked && includeAuxiliarySubtypes) {
191 if (DEBUG) {
192 Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen.");
193 }
194 includeAuxiliarySubtypes = false;
195 }
Yohei Yukawa7eee1812018-11-20 17:32:24 -0800196 final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
197 final int numImes = imis.size();
198 for (int i = 0; i < numImes; ++i) {
199 final InputMethodInfo imi = imis.get(i);
200 final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
201 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
Yohei Yukawa9b89a0b2018-11-20 18:45:59 -0800202 final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
Satoshi Kataokad787f692013-10-26 04:44:21 +0900203 for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
204 enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
205 }
206 final CharSequence imeLabel = imi.loadLabel(mPm);
Yohei Yukawa5f8e7312015-12-10 00:58:55 -0800207 if (enabledSubtypeSet.size() > 0) {
Satoshi Kataokad787f692013-10-26 04:44:21 +0900208 final int subtypeCount = imi.getSubtypeCount();
209 if (DEBUG) {
210 Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
211 }
212 for (int j = 0; j < subtypeCount; ++j) {
213 final InputMethodSubtype subtype = imi.getSubtypeAt(j);
214 final String subtypeHashCode = String.valueOf(subtype.hashCode());
215 // We show all enabled IMEs and subtypes when an IME is shown.
216 if (enabledSubtypeSet.contains(subtypeHashCode)
Seigo Nonaka14e13912015-05-06 21:04:13 -0700217 && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
Satoshi Kataokad787f692013-10-26 04:44:21 +0900218 final CharSequence subtypeLabel =
219 subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
220 .getDisplayName(mContext, imi.getPackageName(),
221 imi.getServiceInfo().applicationInfo);
222 imList.add(new ImeSubtypeListItem(imeLabel,
223 subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
224
225 // Removing this subtype from enabledSubtypeSet because we no
226 // longer need to add an entry of this subtype to imList to avoid
227 // duplicated entries.
228 enabledSubtypeSet.remove(subtypeHashCode);
229 }
230 }
231 } else {
232 imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
233 mSystemLocaleStr));
234 }
235 }
236 Collections.sort(imList);
237 return imList;
238 }
239 }
240
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900241 private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
242 return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
243 subtype.hashCode()) : NOT_A_SUBTYPE_ID;
244 }
Yohei Yukawad1da1152014-05-01 17:20:05 +0900245
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900246 private static class StaticRotationList {
247 private final List<ImeSubtypeListItem> mImeSubtypeList;
248 public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) {
249 mImeSubtypeList = imeSubtypeList;
Yohei Yukawad1da1152014-05-01 17:20:05 +0900250 }
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900251
252 /**
253 * Returns the index of the specified input method and subtype in the given list.
254 * @param imi The {@link InputMethodInfo} to be searched.
255 * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method
256 * does not have a subtype.
257 * @return The index in the given list. -1 if not found.
258 */
259 private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) {
260 final int currentSubtypeId = calculateSubtypeId(imi, subtype);
261 final int N = mImeSubtypeList.size();
262 for (int i = 0; i < N; ++i) {
263 final ImeSubtypeListItem isli = mImeSubtypeList.get(i);
264 // Skip until the current IME/subtype is found.
265 if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) {
266 return i;
Yohei Yukawaa1223cf2014-05-01 20:26:41 +0900267 }
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900268 }
269 return -1;
270 }
271
272 public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700273 InputMethodInfo imi, InputMethodSubtype subtype) {
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900274 if (imi == null) {
275 return null;
276 }
277 if (mImeSubtypeList.size() <= 1) {
278 return null;
279 }
280 final int currentIndex = getIndex(imi, subtype);
281 if (currentIndex < 0) {
282 return null;
283 }
284 final int N = mImeSubtypeList.size();
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700285 for (int offset = 1; offset < N; ++offset) {
Yohei Yukawac7cc0ca2018-05-02 10:55:27 -0700286 // Start searching the next IME/subtype from the next of the current index.
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900287 final int candidateIndex = (currentIndex + offset) % N;
288 final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
Yohei Yukawaa1223cf2014-05-01 20:26:41 +0900289 // Skip if searching inside the current IME only, but the candidate is not
290 // the current IME.
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900291 if (onlyCurrentIme && !imi.equals(candidate.mImi)) {
Yohei Yukawaa1223cf2014-05-01 20:26:41 +0900292 continue;
293 }
294 return candidate;
295 }
Yohei Yukawaa1223cf2014-05-01 20:26:41 +0900296 return null;
Yohei Yukawad1da1152014-05-01 17:20:05 +0900297 }
Yohei Yukawad7248862015-06-03 23:56:12 -0700298
299 protected void dump(final Printer pw, final String prefix) {
300 final int N = mImeSubtypeList.size();
301 for (int i = 0; i < N; ++i) {
302 final int rank = i;
303 final ImeSubtypeListItem item = mImeSubtypeList.get(i);
304 pw.println(prefix + "rank=" + rank + " item=" + item);
305 }
306 }
Yohei Yukawad1da1152014-05-01 17:20:05 +0900307 }
Satoshi Kataokad787f692013-10-26 04:44:21 +0900308
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900309 private static class DynamicRotationList {
310 private static final String TAG = DynamicRotationList.class.getSimpleName();
311 private final List<ImeSubtypeListItem> mImeSubtypeList;
312 private final int[] mUsageHistoryOfSubtypeListItemIndex;
313
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900314 private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) {
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900315 mImeSubtypeList = imeSubtypeListItems;
316 mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()];
317 final int N = mImeSubtypeList.size();
318 for (int i = 0; i < N; i++) {
319 mUsageHistoryOfSubtypeListItemIndex[i] = i;
320 }
321 }
322
323 /**
324 * Returns the index of the specified object in
325 * {@link #mUsageHistoryOfSubtypeListItemIndex}.
326 * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank"
327 * so as not to be confused with the index in {@link #mImeSubtypeList}.
328 * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually.
329 */
330 private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
331 final int currentSubtypeId = calculateSubtypeId(imi, subtype);
332 final int N = mUsageHistoryOfSubtypeListItemIndex.length;
333 for (int usageRank = 0; usageRank < N; usageRank++) {
334 final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
335 final ImeSubtypeListItem subtypeListItem =
336 mImeSubtypeList.get(subtypeListItemIndex);
337 if (subtypeListItem.mImi.equals(imi) &&
338 subtypeListItem.mSubtypeId == currentSubtypeId) {
339 return usageRank;
340 }
341 }
342 // Not found in the known IME/Subtype list.
343 return -1;
344 }
345
346 public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) {
347 final int currentUsageRank = getUsageRank(imi, subtype);
348 // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0
349 if (currentUsageRank <= 0) {
350 return;
351 }
352 final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank];
353 System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0,
354 mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank);
355 mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex;
356 }
357
358 public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700359 InputMethodInfo imi, InputMethodSubtype subtype) {
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900360 int currentUsageRank = getUsageRank(imi, subtype);
361 if (currentUsageRank < 0) {
362 if (DEBUG) {
363 Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype);
364 }
365 return null;
366 }
367 final int N = mUsageHistoryOfSubtypeListItemIndex.length;
368 for (int i = 1; i < N; i++) {
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700369 final int subtypeListItemRank = (currentUsageRank + i) % N;
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900370 final int subtypeListItemIndex =
371 mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
372 final ImeSubtypeListItem subtypeListItem =
373 mImeSubtypeList.get(subtypeListItemIndex);
374 if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) {
375 continue;
376 }
377 return subtypeListItem;
378 }
379 return null;
380 }
Yohei Yukawad7248862015-06-03 23:56:12 -0700381
382 protected void dump(final Printer pw, final String prefix) {
383 for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) {
384 final int rank = mUsageHistoryOfSubtypeListItemIndex[i];
385 final ImeSubtypeListItem item = mImeSubtypeList.get(i);
386 pw.println(prefix + "rank=" + rank + " item=" + item);
387 }
388 }
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900389 }
390
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900391 @VisibleForTesting
392 public static class ControllerImpl {
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900393 private final DynamicRotationList mSwitchingAwareRotationList;
394 private final StaticRotationList mSwitchingUnawareRotationList;
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900395
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900396 public static ControllerImpl createFrom(final ControllerImpl currentInstance,
397 final List<ImeSubtypeListItem> sortedEnabledItems) {
398 DynamicRotationList switchingAwareRotationList = null;
399 {
400 final List<ImeSubtypeListItem> switchingAwareImeSubtypes =
401 filterImeSubtypeList(sortedEnabledItems,
402 true /* supportsSwitchingToNextInputMethod */);
403 if (currentInstance != null &&
404 currentInstance.mSwitchingAwareRotationList != null &&
405 Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
406 switchingAwareImeSubtypes)) {
407 // Can reuse the current instance.
408 switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList;
409 }
410 if (switchingAwareRotationList == null) {
411 switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes);
412 }
413 }
414
415 StaticRotationList switchingUnawareRotationList = null;
416 {
417 final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList(
418 sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */);
419 if (currentInstance != null &&
420 currentInstance.mSwitchingUnawareRotationList != null &&
421 Objects.equals(
422 currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList,
423 switchingUnawareImeSubtypes)) {
424 // Can reuse the current instance.
425 switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList;
426 }
427 if (switchingUnawareRotationList == null) {
428 switchingUnawareRotationList =
429 new StaticRotationList(switchingUnawareImeSubtypes);
430 }
431 }
432
433 return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList);
434 }
435
436 private ControllerImpl(final DynamicRotationList switchingAwareRotationList,
437 final StaticRotationList switchingUnawareRotationList) {
438 mSwitchingAwareRotationList = switchingAwareRotationList;
439 mSwitchingUnawareRotationList = switchingUnawareRotationList;
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900440 }
441
442 public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi,
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700443 InputMethodSubtype subtype) {
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900444 if (imi == null) {
445 return null;
446 }
447 if (imi.supportsSwitchingToNextInputMethod()) {
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900448 return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700449 subtype);
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900450 } else {
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900451 return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700452 subtype);
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900453 }
454 }
455
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900456 public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
457 if (imi == null) {
458 return;
459 }
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900460 if (imi.supportsSwitchingToNextInputMethod()) {
461 mSwitchingAwareRotationList.onUserAction(imi, subtype);
462 }
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900463 }
464
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900465 private static List<ImeSubtypeListItem> filterImeSubtypeList(
466 final List<ImeSubtypeListItem> items,
467 final boolean supportsSwitchingToNextInputMethod) {
468 final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
469 final int ALL_ITEMS_COUNT = items.size();
470 for (int i = 0; i < ALL_ITEMS_COUNT; i++) {
471 final ImeSubtypeListItem item = items.get(i);
472 if (item.mImi.supportsSwitchingToNextInputMethod() ==
473 supportsSwitchingToNextInputMethod) {
474 result.add(item);
475 }
476 }
477 return result;
478 }
Yohei Yukawad7248862015-06-03 23:56:12 -0700479
480 protected void dump(final Printer pw) {
481 pw.println(" mSwitchingAwareRotationList:");
482 mSwitchingAwareRotationList.dump(pw, " ");
483 pw.println(" mSwitchingUnawareRotationList:");
484 mSwitchingUnawareRotationList.dump(pw, " ");
485 }
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900486 }
487
488 private final InputMethodSettings mSettings;
489 private InputMethodAndSubtypeList mSubtypeList;
490 private ControllerImpl mController;
491
Yohei Yukawa5a647b692014-05-22 12:49:00 +0900492 private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
Satoshi Kataokad787f692013-10-26 04:44:21 +0900493 mSettings = settings;
Yohei Yukawa5a647b692014-05-22 12:49:00 +0900494 resetCircularListLocked(context);
495 }
496
497 public static InputMethodSubtypeSwitchingController createInstanceLocked(
498 InputMethodSettings settings, Context context) {
499 return new InputMethodSubtypeSwitchingController(settings, context);
Satoshi Kataokad787f692013-10-26 04:44:21 +0900500 }
Satoshi Kataokad7443c82013-10-15 17:45:43 +0900501
Yohei Yukawa02970512014-06-05 16:16:18 +0900502 public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
Yohei Yukawaa9bda772014-05-23 18:35:13 +0900503 if (mController == null) {
504 if (DEBUG) {
505 Log.e(TAG, "mController shouldn't be null.");
506 }
507 return;
508 }
509 mController.onUserActionLocked(imi, subtype);
Satoshi Kataokad7443c82013-10-15 17:45:43 +0900510 }
Satoshi Kataokad787f692013-10-26 04:44:21 +0900511
512 public void resetCircularListLocked(Context context) {
Yohei Yukawa5a647b692014-05-22 12:49:00 +0900513 mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
Yohei Yukawa07bd7322014-06-02 15:32:59 +0900514 mController = ControllerImpl.createFrom(mController,
Yohei Yukawae512f852015-12-10 01:01:02 -0800515 mSubtypeList.getSortedInputMethodAndSubtypeList(
516 false /* includeAuxiliarySubtypes */, false /* isScreenLocked */));
Satoshi Kataokad787f692013-10-26 04:44:21 +0900517 }
518
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900519 public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700520 InputMethodSubtype subtype) {
Yohei Yukawa9b29d042014-05-22 20:26:39 +0900521 if (mController == null) {
522 if (DEBUG) {
523 Log.e(TAG, "mController shouldn't be null.");
524 }
525 return null;
526 }
Yohei Yukawa136b6ce2018-05-02 10:55:35 -0700527 return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
Satoshi Kataokad787f692013-10-26 04:44:21 +0900528 }
529
Yohei Yukawa5f8e7312015-12-10 00:58:55 -0800530 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(
Seigo Nonaka14e13912015-05-06 21:04:13 -0700531 boolean includingAuxiliarySubtypes, boolean isScreenLocked) {
Yohei Yukawa5a647b692014-05-22 12:49:00 +0900532 return mSubtypeList.getSortedInputMethodAndSubtypeList(
Yohei Yukawa5f8e7312015-12-10 00:58:55 -0800533 includingAuxiliarySubtypes, isScreenLocked);
Satoshi Kataokad787f692013-10-26 04:44:21 +0900534 }
Yohei Yukawad7248862015-06-03 23:56:12 -0700535
536 public void dump(final Printer pw) {
537 if (mController != null) {
538 mController.dump(pw);
539 } else {
540 pw.println(" mController=null");
541 }
542 }
Satoshi Kataokad7443c82013-10-15 17:45:43 +0900543}