/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.inputmethod; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; import android.util.Printer; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.TreeMap; /** * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes. *
* This class is designed to be used from and only from {@link InputMethodManagerService} by using * {@link InputMethodManagerService#mMethodMap} as a global lock. *
*/ public class InputMethodSubtypeSwitchingController { private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName(); private static final boolean DEBUG = false; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; public static class ImeSubtypeListItem implements ComparableWe call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank"
* so as not to be confused with the index in {@link #mImeSubtypeList}.
* @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually.
*/
private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
final int currentSubtypeId = calculateSubtypeId(imi, subtype);
final int N = mUsageHistoryOfSubtypeListItemIndex.length;
for (int usageRank = 0; usageRank < N; usageRank++) {
final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
final ImeSubtypeListItem subtypeListItem =
mImeSubtypeList.get(subtypeListItemIndex);
if (subtypeListItem.mImi.equals(imi) &&
subtypeListItem.mSubtypeId == currentSubtypeId) {
return usageRank;
}
}
// Not found in the known IME/Subtype list.
return -1;
}
public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) {
final int currentUsageRank = getUsageRank(imi, subtype);
// Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0
if (currentUsageRank <= 0) {
return;
}
final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank];
System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0,
mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank);
mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex;
}
/**
* Provides the basic operation to implement bi-directional IME rotation.
* @param onlyCurrentIme {@code true} to limit the search space to IME subtypes that belong
* to {@code imi}.
* @param imi {@link InputMethodInfo} that will be used in conjunction with {@code subtype}
* from which we find the adjacent IME subtype.
* @param subtype {@link InputMethodSubtype} that will be used in conjunction with
* {@code imi} from which we find the next IME subtype. {@code null} if the input method
* does not have a subtype.
* @param forward {@code true} to do forward search the next IME subtype. Specify
* {@code false} to do backward search.
* @return The IME subtype found. {@code null} if no IME subtype is found.
*/
@Nullable
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean forward) {
int currentUsageRank = getUsageRank(imi, subtype);
if (currentUsageRank < 0) {
if (DEBUG) {
Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype);
}
return null;
}
final int N = mUsageHistoryOfSubtypeListItemIndex.length;
for (int i = 1; i < N; i++) {
final int offset = forward ? i : N - i;
final int subtypeListItemRank = (currentUsageRank + offset) % N;
final int subtypeListItemIndex =
mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
final ImeSubtypeListItem subtypeListItem =
mImeSubtypeList.get(subtypeListItemIndex);
if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) {
continue;
}
return subtypeListItem;
}
return null;
}
protected void dump(final Printer pw, final String prefix) {
for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) {
final int rank = mUsageHistoryOfSubtypeListItemIndex[i];
final ImeSubtypeListItem item = mImeSubtypeList.get(i);
pw.println(prefix + "rank=" + rank + " item=" + item);
}
}
}
@VisibleForTesting
public static class ControllerImpl {
private final DynamicRotationList mSwitchingAwareRotationList;
private final StaticRotationList mSwitchingUnawareRotationList;
public static ControllerImpl createFrom(final ControllerImpl currentInstance,
final List