Loading services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.server.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputMethodSubtypeHandle; import java.util.ArrayList; import java.util.List; import java.util.Objects; final class HardwareKeyboardShortcutController { @GuardedBy("ImfLock.class") private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>(); @GuardedBy("ImfLock.class") void reset(@NonNull InputMethodUtils.InputMethodSettings settings) { mSubtypeHandles.clear(); for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) { if (!imi.shouldShowInInputMethodPicker()) { continue; } final List<InputMethodSubtype> subtypes = settings.getEnabledInputMethodSubtypeListLocked(imi, true); if (subtypes.isEmpty()) { mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null)); } else { for (final InputMethodSubtype subtype : subtypes) { if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) { mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, subtype)); } } } } } @AnyThread @Nullable static <T> T getNeighborItem(@NonNull List<T> list, @NonNull T value, boolean next) { final int size = list.size(); for (int i = 0; i < size; ++i) { if (Objects.equals(value, list.get(i))) { final int nextIndex = (i + (next ? 1 : -1) + size) % size; return list.get(nextIndex); } } return null; } @GuardedBy("ImfLock.class") @Nullable InputMethodSubtypeHandle onSubtypeSwitch( @NonNull InputMethodSubtypeHandle currentImeAndSubtype, boolean forward) { return getNeighborItem(mSubtypeHandles, currentImeAndSubtype, forward); } } services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +36 −4 Original line number Diff line number Diff line Loading @@ -316,6 +316,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); final InputMethodSubtypeSwitchingController mSwitchingController; final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(); /** * Tracks how many times {@link #mMethodMap} was updated. Loading Loading @@ -1731,6 +1733,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context); mHardwareKeyboardShortcutController.reset(mSettings); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null Loading Loading @@ -3268,6 +3271,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Make sure that mSwitchingController and mSettings are sharing the // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); mHardwareKeyboardShortcutController.reset(mSettings); sendOnNavButtonFlagsChangedLocked(); } Loading Loading @@ -5293,6 +5297,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Make sure that mSwitchingController and mSettings are sharing the // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); mHardwareKeyboardShortcutController.reset(mSettings); sendOnNavButtonFlagsChangedLocked(); Loading Loading @@ -5827,10 +5832,37 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void switchKeyboardLayout(int direction) { synchronized (ImfLock.class) { if (direction > 0) { switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */); } else { // TODO(b/258853866): Support backwards switching. final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked()); if (currentImi == null) { return; } final InputMethodSubtypeHandle currentSubtypeHandle = InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype); final InputMethodSubtypeHandle nextSubtypeHandle = mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle, direction > 0); if (nextSubtypeHandle == null) { return; } final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId()); if (nextImi == null) { return; } final int subtypeCount = nextImi.getSubtypeCount(); if (subtypeCount == 0) { if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) { setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID); } return; } for (int i = 0; i < subtypeCount; ++i) { if (nextSubtypeHandle.equals( InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) { setInputMethodLocked(nextImi.getId(), i); return; } } } } Loading services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java→services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java +57 −0 Original line number Diff line number Diff line Loading @@ -16,24 +16,42 @@ package com.android.server.inputmethod; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase { public final class HardwareKeyboardShortcutControllerTest { @Test public void testForwardRotation() { final List<String> handles = Arrays.asList("0", "1", "2", "3"); assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", true)); assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "2", true)); assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", true)); } @Test public void testBackwardRotation() { final List<String> handles = Arrays.asList("0", "1", "2", "3"); assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", false)); assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "0", false)); assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", false)); } @Test public void testSwitchToNextKeyboardLayout() { ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController); InputMethodManagerInternal.get().switchKeyboardLayout(1); verify(mInputMethodManagerService.mSwitchingController) .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any()); public void testNotMatching() { final List<String> handles = Arrays.asList("0", "1", "2", "3"); assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", true)); assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", false)); } } Loading
services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.server.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputMethodSubtypeHandle; import java.util.ArrayList; import java.util.List; import java.util.Objects; final class HardwareKeyboardShortcutController { @GuardedBy("ImfLock.class") private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>(); @GuardedBy("ImfLock.class") void reset(@NonNull InputMethodUtils.InputMethodSettings settings) { mSubtypeHandles.clear(); for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) { if (!imi.shouldShowInInputMethodPicker()) { continue; } final List<InputMethodSubtype> subtypes = settings.getEnabledInputMethodSubtypeListLocked(imi, true); if (subtypes.isEmpty()) { mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null)); } else { for (final InputMethodSubtype subtype : subtypes) { if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) { mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, subtype)); } } } } } @AnyThread @Nullable static <T> T getNeighborItem(@NonNull List<T> list, @NonNull T value, boolean next) { final int size = list.size(); for (int i = 0; i < size; ++i) { if (Objects.equals(value, list.get(i))) { final int nextIndex = (i + (next ? 1 : -1) + size) % size; return list.get(nextIndex); } } return null; } @GuardedBy("ImfLock.class") @Nullable InputMethodSubtypeHandle onSubtypeSwitch( @NonNull InputMethodSubtypeHandle currentImeAndSubtype, boolean forward) { return getNeighborItem(mSubtypeHandles, currentImeAndSubtype, forward); } }
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +36 −4 Original line number Diff line number Diff line Loading @@ -316,6 +316,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); final InputMethodSubtypeSwitchingController mSwitchingController; final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(); /** * Tracks how many times {@link #mMethodMap} was updated. Loading Loading @@ -1731,6 +1733,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context); mHardwareKeyboardShortcutController.reset(mSettings); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null Loading Loading @@ -3268,6 +3271,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Make sure that mSwitchingController and mSettings are sharing the // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); mHardwareKeyboardShortcutController.reset(mSettings); sendOnNavButtonFlagsChangedLocked(); } Loading Loading @@ -5293,6 +5297,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Make sure that mSwitchingController and mSettings are sharing the // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); mHardwareKeyboardShortcutController.reset(mSettings); sendOnNavButtonFlagsChangedLocked(); Loading Loading @@ -5827,10 +5832,37 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void switchKeyboardLayout(int direction) { synchronized (ImfLock.class) { if (direction > 0) { switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */); } else { // TODO(b/258853866): Support backwards switching. final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked()); if (currentImi == null) { return; } final InputMethodSubtypeHandle currentSubtypeHandle = InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype); final InputMethodSubtypeHandle nextSubtypeHandle = mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle, direction > 0); if (nextSubtypeHandle == null) { return; } final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId()); if (nextImi == null) { return; } final int subtypeCount = nextImi.getSubtypeCount(); if (subtypeCount == 0) { if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) { setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID); } return; } for (int i = 0; i < subtypeCount; ++i) { if (nextSubtypeHandle.equals( InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) { setInputMethodLocked(nextImi.getId(), i); return; } } } } Loading
services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java→services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java +57 −0 Original line number Diff line number Diff line Loading @@ -16,24 +16,42 @@ package com.android.server.inputmethod; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase { public final class HardwareKeyboardShortcutControllerTest { @Test public void testForwardRotation() { final List<String> handles = Arrays.asList("0", "1", "2", "3"); assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", true)); assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "2", true)); assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", true)); } @Test public void testBackwardRotation() { final List<String> handles = Arrays.asList("0", "1", "2", "3"); assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", false)); assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "0", false)); assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", false)); } @Test public void testSwitchToNextKeyboardLayout() { ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController); InputMethodManagerInternal.get().switchKeyboardLayout(1); verify(mInputMethodManagerService.mSwitchingController) .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any()); public void testNotMatching() { final List<String> handles = Arrays.asList("0", "1", "2", "3"); assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", true)); assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", false)); } }