diff options
| author | Yohei Yukawa <yukawa@google.com> | 2014-05-23 18:35:13 +0900 |
|---|---|---|
| committer | Yohei Yukawa <yukawa@google.com> | 2014-05-27 01:32:30 +0900 |
| commit | a9bda774276f1c5a1fc6fd67a7782a06e696be8f (patch) | |
| tree | d6da862231d4f340ca7f27d900aad825aa0c14f6 /core | |
| parent | bd57735c6c90669f0e2ff0227df952755df796b4 (diff) | |
| download | frameworks_base-a9bda774276f1c5a1fc6fd67a7782a06e696be8f.zip frameworks_base-a9bda774276f1c5a1fc6fd67a7782a06e696be8f.tar.gz frameworks_base-a9bda774276f1c5a1fc6fd67a7782a06e696be8f.tar.bz2 | |
Implement dynamic IME rotation based on user action
With this CL, the IME rotation order will be updated dynamically
based on user actions on IMEs. Currently only onCommitText is
took into considered.
Imagine that we have the following rotation order.
[A, B, C, D, E]
If a user action for C is observed, the rotation order will be
updated as follows:
[C, A, B, D, E]
Then another user action for D updates the rotation order as
follows:
[D, C, A, B, E]
BUG: 7043015
Change-Id: Ic005b94379f9d847ea87046473ed77d8018d930e
Diffstat (limited to 'core')
2 files changed, 182 insertions, 25 deletions
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 70c77b8..bfa5c22 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -271,13 +271,87 @@ public class InputMethodSubtypeSwitchingController { } } + private static class DynamicRotationList { + private static final String TAG = DynamicRotationList.class.getSimpleName(); + private final List<ImeSubtypeListItem> mImeSubtypeList; + private final int[] mUsageHistoryOfSubtypeListItemIndex; + + public DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + mImeSubtypeList = imeSubtypeListItems; + mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; i++) { + mUsageHistoryOfSubtypeListItemIndex[i] = i; + } + } + + /** + * Returns the index of the specified object in + * {@link #mUsageHistoryOfSubtypeListItemIndex}. + * <p>We 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; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + 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 subtypeListItemRank = (currentUsageRank + i) % N; + final int subtypeListItemIndex = + mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) { + continue; + } + return subtypeListItem; + } + return null; + } + } + @VisibleForTesting public static class ControllerImpl { - private final StaticRotationList mSwitchingAwareSubtypeList; + private final DynamicRotationList mSwitchingAwareSubtypeList; private final StaticRotationList mSwitchingUnawareSubtypeList; public ControllerImpl(final List<ImeSubtypeListItem> sortedItems) { - mSwitchingAwareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, + mSwitchingAwareSubtypeList = new DynamicRotationList(filterImeSubtypeList(sortedItems, true /* supportsSwitchingToNextInputMethod */)); mSwitchingUnawareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, false /* supportsSwitchingToNextInputMethod */)); @@ -297,6 +371,15 @@ public class InputMethodSubtypeSwitchingController { } } + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return; + } + if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareSubtypeList.onUserAction(imi, subtype); + } + } + private static List<ImeSubtypeListItem> filterImeSubtypeList( final List<ImeSubtypeListItem> items, final boolean supportsSwitchingToNextInputMethod) { @@ -327,9 +410,14 @@ public class InputMethodSubtypeSwitchingController { return new InputMethodSubtypeSwitchingController(settings, context); } - // TODO: write unit tests for this method and the logic that determines the next subtype public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) { - // TODO: Implement this. + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return; + } + mController.onUserActionLocked(imi, subtype); } public void resetCircularListLocked(Context context) { diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java index 459bed5..04cfe05 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java @@ -132,6 +132,29 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe assertEquals(nextItem, nextIme); } + private void assertRotationOrder(final ControllerImpl controller, + final boolean onlyCurrentIme, + final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { + final int N = expectedRotationOrderOfImeSubtypeList.length; + for (int i = 0; i < N; i++) { + final int currentIndex = i; + final int nextIndex = (currentIndex + 1) % N; + final ImeSubtypeListItem currentItem = + expectedRotationOrderOfImeSubtypeList[currentIndex]; + final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex]; + assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem); + } + } + + private void onUserAction(final ControllerImpl controller, + final ImeSubtypeListItem subtypeListItem) { + InputMethodSubtype subtype = null; + if (subtypeListItem.mSubtypeName != null) { + subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString()); + } + controller.onUserActionLocked(subtypeListItem.mImi, subtype); + } + @SmallTest public void testControllerImpl() throws Exception { final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); @@ -152,33 +175,20 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe final ControllerImpl controller = new ControllerImpl(enabledItems); // switching-aware loop - assertNextInputMethod(controller, false /* onlyCurrentIme */, - latinIme_en_US, latinIme_fr); - assertNextInputMethod(controller, false /* onlyCurrentIme */, - latinIme_fr, japaneseIme_ja_JP); - assertNextInputMethod(controller, false /* onlyCurrentIme */, - japaneseIme_ja_JP, latinIme_en_US); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); // switching-unaware loop - assertNextInputMethod(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); - assertNextInputMethod(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_hi, subtypeUnawareIme); - assertNextInputMethod(controller, false /* onlyCurrentIme */, - subtypeUnawareIme, switchUnawareJapaneseIme_ja_JP); - assertNextInputMethod(controller, false /* onlyCurrentIme */, - switchUnawareJapaneseIme_ja_JP, switchingUnawarelatinIme_en_UK); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); // test onlyCurrentIme == true - assertNextInputMethod(controller, true /* onlyCurrentIme */, + assertRotationOrder(controller, true /* onlyCurrentIme */, latinIme_en_US, latinIme_fr); - assertNextInputMethod(controller, true /* onlyCurrentIme */, - latinIme_fr, latinIme_en_US); - assertNextInputMethod(controller, true /* onlyCurrentIme */, + assertRotationOrder(controller, true /* onlyCurrentIme */, switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, - switchingUnawarelatinIme_hi, switchingUnawarelatinIme_en_UK); - assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, japaneseIme_ja_JP, null); @@ -203,4 +213,63 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledSubtypeUnawareIme, null); } - } + + @SmallTest + public void testControllerImplWithUserAction() throws Exception { + final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); + final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); + final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); + final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + + final ControllerImpl controller = new ControllerImpl(enabledItems); + + // === switching-aware loop === + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + // Then notify that a user did something for latinIme_fr. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + // Then notify that a user did something for latinIme_fr again. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + // Then notify that a user did something for japaneseIme_ja_JP. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + // Check onlyCurrentIme == true. + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_JP, null); + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US); + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + + // === switching-unaware loop === + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // User action should be ignored for switching unaware IMEs. + onUserAction(controller, switchingUnawarelatinIme_hi); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // User action should be ignored for switching unaware IMEs. + onUserAction(controller, switchUnawareJapaneseIme_ja_JP); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // Check onlyCurrentIme == true. + assertRotationOrder(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, null); + } +} |
