| // Copyright 2010-2014, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package org.mozc.android.inputmethod.japanese.keyboard; |
| |
| import static org.easymock.EasyMock.capture; |
| import static org.easymock.EasyMock.eq; |
| import static org.easymock.EasyMock.expectLastCall; |
| import static org.easymock.EasyMock.isA; |
| import static org.easymock.EasyMock.same; |
| |
| import org.mozc.android.inputmethod.japanese.MozcUtil; |
| import org.mozc.android.inputmethod.japanese.keyboard.BackgroundDrawableFactory.DrawableType; |
| import org.mozc.android.inputmethod.japanese.keyboard.Flick.Direction; |
| import org.mozc.android.inputmethod.japanese.keyboard.Key.Stick; |
| import org.mozc.android.inputmethod.japanese.keyboard.KeyState.MetaState; |
| import org.mozc.android.inputmethod.japanese.keyboard.Keyboard.KeyboardSpecification; |
| import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchAction; |
| import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchEvent; |
| import org.mozc.android.inputmethod.japanese.testing.InstrumentationTestCaseWithMock; |
| import org.mozc.android.inputmethod.japanese.testing.MockResourcesWithDisplayMetrics; |
| import org.mozc.android.inputmethod.japanese.testing.Parameter; |
| import org.mozc.android.inputmethod.japanese.testing.VisibilityProxy; |
| import org.mozc.android.inputmethod.japanese.view.DrawableCache; |
| import org.mozc.android.inputmethod.japanese.view.SkinType; |
| import com.google.common.base.Optional; |
| |
| import android.content.res.Resources; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.test.mock.MockResources; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.text.InputType; |
| import android.util.DisplayMetrics; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.inputmethod.EditorInfo; |
| |
| import org.easymock.Capture; |
| import org.easymock.EasyMock; |
| import org.easymock.IAnswer; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Unit tests for {@code org.mozc.android.inputmethod.japanese.keyboard.KeyboardView}. |
| */ |
| public class KeyboardViewTest extends InstrumentationTestCaseWithMock { |
| |
| /** |
| * The number of steps of a drag path. |
| */ |
| private static final int STEP_COUNT = 20; |
| |
| // The following are parameters of a dummy key. |
| private static final int WIDTH = 50; |
| private static final int HEIGHT = 30; |
| private static final int HORIZONTAL_GAP = 0; |
| private static final int VERTICAL_GAP = 0; |
| |
| private KeyboardView view; |
| |
| private static boolean touchEvent(KeyboardView view, int action, int x, int y) { |
| return touchEvent(view, 0, 0, action, x, y); |
| } |
| |
| private static boolean touchEvent( |
| KeyboardView view, long downTime, long eventTime, int action, int x, int y) { |
| MotionEvent e = MotionEvent.obtain(downTime, eventTime, action, x, y, 0); |
| try { |
| return view.onTouchEvent(e); |
| } finally { |
| e.recycle(); |
| } |
| } |
| |
| /** Emulates dragging from @{code (fromX, fromY)} to @{code (toX, toY)}. */ |
| private static boolean drag(KeyboardView view, int fromX, int toX, int fromY, int toY) { |
| long downTime = MozcUtil.getUptimeMillis(); |
| |
| boolean result = true; |
| result &= touchEvent(view, downTime, downTime, MotionEvent.ACTION_DOWN, fromX, fromY); |
| |
| for (int i = 0; i < STEP_COUNT; ++i) { |
| int x = fromX + (toX - fromX) * i / STEP_COUNT; |
| int y = fromY + (toY - fromY) * i / STEP_COUNT; |
| |
| long eventTime = MozcUtil.getUptimeMillis(); |
| result &= touchEvent(view, downTime, eventTime, MotionEvent.ACTION_MOVE, x, y); |
| } |
| |
| long eventTime = MozcUtil.getUptimeMillis(); |
| result &= touchEvent(view, downTime, eventTime, MotionEvent.ACTION_UP, toX, toY); |
| return result; |
| } |
| |
| private static KeyEntity createInvalidKeyEntity(int sourceId, int keyCode) { |
| return new KeyEntity( |
| sourceId, keyCode, KeyEntity.INVALID_KEY_CODE, true, 0, |
| Optional.<String>absent(), false, Optional.<PopUp>absent(), 0, 0, 0, 0); |
| } |
| |
| private static KeyState createKeyStateWithKeyEntity( |
| Set<MetaState> metaState, KeyEntity keyEntity) { |
| Flick flick = new Flick(Flick.Direction.CENTER, keyEntity); |
| return new KeyState( |
| "", |
| metaState, |
| Collections.<MetaState>emptySet(), |
| Collections.<MetaState>emptySet(), |
| Collections.singletonList(flick)); |
| } |
| |
| private static KeyState createKeyState(Set<MetaState> metaState, int keyCode) { |
| return createKeyStateWithKeyEntity(metaState, createInvalidKeyEntity(1, keyCode)); |
| } |
| |
| private static Key createKey(int x, int y, int keyCode) { |
| return new Key( |
| x, y, WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, false, Stick.EVEN, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Collections.singletonList(createKeyState(Collections.<MetaState>emptySet(), keyCode))); |
| } |
| |
| private static Key createKeyWithKeyEntity(int x, int y, KeyEntity keyEntity) { |
| return new Key( |
| x, y, WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, false, Stick.EVEN, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Collections.singletonList( |
| createKeyStateWithKeyEntity(Collections.<MetaState>emptySet(), keyEntity))); |
| } |
| |
| private static Key createSpacer(int x, int y, Stick stick) { |
| return new Key( |
| x, y, WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, false, stick, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Collections.<KeyState>emptyList()); |
| } |
| |
| private static Key createKeyWithModifiedState(int x, int y, int keyCode, int modifiedKeyCode) { |
| return new Key( |
| x, y, WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, false, Stick.EVEN, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Arrays.asList(createKeyState(Collections.<MetaState>emptySet(), keyCode), |
| createKeyState(EnumSet.of(MetaState.CAPS_LOCK), modifiedKeyCode))); |
| } |
| |
| private static Key createFlickKey( |
| int x, int y, |
| int centerKeyCode, int leftKeyCode, int rightKeyCode, int upKeyCode, int downKeyCode) { |
| Flick center = new Flick(Flick.Direction.CENTER, createInvalidKeyEntity(1, centerKeyCode)); |
| Flick left = new Flick(Flick.Direction.LEFT, createInvalidKeyEntity(1, leftKeyCode)); |
| Flick right = new Flick(Flick.Direction.RIGHT, createInvalidKeyEntity(1, rightKeyCode)); |
| Flick up = new Flick(Flick.Direction.UP, createInvalidKeyEntity(1, upKeyCode)); |
| Flick down = new Flick(Flick.Direction.DOWN, createInvalidKeyEntity(1, downKeyCode)); |
| KeyState keyState = new KeyState( |
| "", |
| Collections.<MetaState>emptySet(), |
| Collections.<MetaState>emptySet(), |
| Collections.<MetaState>emptySet(), |
| Arrays.asList(center, left, right, up, down)); |
| return new Key(x, y, WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, false, Stick.EVEN, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Collections.singletonList(keyState)); |
| } |
| |
| private static Key createModifierKey(int x, int y, int keyCode, Set<MetaState> nextAddMetaStates, |
| Set<MetaState> nextRemoveMetaStates) { |
| KeyEntity entity = createInvalidKeyEntity(1, keyCode); |
| Flick flick = new Flick(Flick.Direction.CENTER, entity); |
| KeyState keyState = new KeyState( |
| "", |
| Collections.<MetaState>emptySet(), |
| nextAddMetaStates, |
| nextRemoveMetaStates, |
| Collections.singletonList(flick)); |
| return new Key(x, y, WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, true, Stick.EVEN, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Collections.singletonList(keyState)); |
| } |
| |
| /** |
| * Creates a dummy keyboard which has only {@code key}. |
| * @return a new dummy keyboard. |
| */ |
| private static Keyboard createDummyKeyboard(Key key) { |
| Row row = new Row(Collections.singletonList(key), HEIGHT, VERTICAL_GAP); |
| return new Keyboard(Optional.<String>absent(), Collections.singletonList(row), 1, |
| KeyboardSpecification.TWELVE_KEY_TOGGLE_FLICK_KANA); |
| } |
| |
| private KeyEventHandler createKeyEventHandlerMock() { |
| KeyboardActionListener keyboardActionListener = createNiceMock(KeyboardActionListener.class); |
| return createMockBuilder(KeyEventHandler.class) |
| .withConstructor(Looper.class, KeyboardActionListener.class, |
| int.class, int.class, int.class) |
| .withArgs(Looper.myLooper(), keyboardActionListener, 0, 0, 0) |
| .createMock(); |
| } |
| |
| private KeyboardViewBackgroundSurface createKeyboardViewBackgroundSurfaceMock() { |
| return createMockBuilder(KeyboardViewBackgroundSurface.class) |
| .withConstructor(BackgroundDrawableFactory.class, DrawableCache.class) |
| .withArgs(new BackgroundDrawableFactory(new MockResourcesWithDisplayMetrics()), |
| new DrawableCache(getInstrumentation().getContext().getResources())) |
| .createMock(); |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| view = new KeyboardView(getInstrumentation().getTargetContext()); |
| view.setKeyboard(createDummyKeyboard(createKey(0, 0, 'a'))); |
| view.layout(0, 0, 100, 60); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| view = null; |
| super.tearDown(); |
| } |
| |
| @SmallTest |
| public void testFlushPendingKeyEventRecursiveCall() { |
| Key targetKey = view.getKeyboard().get().getRowList().get(0).getKeyList().get(0); |
| KeyEventContext keyEventContext = |
| new KeyEventContext(targetKey, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| keyEventHandler.sendKey( |
| keyEventContext.getKeyCode(), |
| Collections.singletonList(keyEventContext.getTouchEvent().get())); |
| expectLastCall().andAnswer(new IAnswer<Void>() { |
| @Override |
| public Void answer() throws Throwable { |
| // Set Keyboard in this callback which invokes flushPendingKeyEvent again. |
| Key key = createKeyWithModifiedState(0, 0, 'a', 'A'); |
| view.setKeyboard(createDummyKeyboard(key)); |
| return null; |
| } |
| }); |
| keyEventHandler.sendRelease(keyEventContext.getPressedKeyCode()); |
| replayAll(); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| view.metaState = EnumSet.of(MetaState.CAPS_LOCK); |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, keyEventContext); |
| |
| Key key = createKeyWithModifiedState(0, 0, 'a', 'A'); |
| view.setKeyboard(createDummyKeyboard(key)); |
| |
| verifyAll(); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_press() { |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| keyEventHandler.maybeStartDelayedKeyEvent(isA(KeyEventContext.class)); |
| keyEventHandler.sendPress('a'); |
| replayAll(); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // By pressing a key, internal pressedKey field should be filled. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| assertTrue(keyEventContextMap.isEmpty()); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 25, 15)); |
| |
| verifyAll(); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.CENTER, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| assertTrue(view.isKeyPressed); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_pressModified() { |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| keyEventHandler.maybeStartDelayedKeyEvent(isA(KeyEventContext.class)); |
| keyEventHandler.sendPress('A'); |
| replayAll(); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| Key key = createKeyWithModifiedState(0, 0, 'a', 'A'); |
| view.setKeyboard(createDummyKeyboard(key)); |
| |
| // Set modified state. |
| view.metaState = EnumSet.of(MetaState.CAPS_LOCK); |
| |
| // By pressing a key, internal pressedKey field should be filled. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| assertTrue(keyEventContextMap.isEmpty()); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 25, 15)); |
| |
| verifyAll(); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.CENTER, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_pressModifier() { |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| keyEventHandler.sendKey( |
| 'a', |
| Arrays.asList( |
| // A TouchEvent for the modifier Key. |
| TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 25, 15, 100, 60, 0)) |
| .build(), |
| // A TouchEvent for 'a' key. |
| TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 100, 60, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease('a'); |
| keyEventHandler.maybeStartDelayedKeyEvent(isA(KeyEventContext.class)); |
| keyEventHandler.sendPress(-1); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| view.setKeyboard(createDummyKeyboard( |
| createModifierKey(0, 0, -1, EnumSet.of(MetaState.CAPS_LOCK), |
| EnumSet.of(MetaState.SHIFT)))); |
| |
| // Set modified state. |
| view.metaState = EnumSet.of(MetaState.SHIFT); |
| |
| KeyEventContext pendingKeyEventContext = |
| new KeyEventContext(createKey(0, 0, 'a'), 1, 100, 60, 100, 60, 0, |
| Collections.<MetaState>emptySet()); |
| // By pressing a key, the pending events should be flushed and |
| // the metaState should be updated. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(1, pendingKeyEventContext); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 25, 15)); |
| |
| verifyAll(); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(EnumSet.of(MetaState.CAPS_LOCK, MetaState.HANDLING_TOUCH_EVENT), view.metaState); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_pressOutside() { |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Pressing non-key region shouldn't cause filling the pressedKey. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| assertTrue(keyEventContextMap.isEmpty()); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 100, -100)); |
| |
| verifyAll(); |
| assertTrue(keyEventContextMap.isEmpty()); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_pressBelowOutside() { |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| keyEventHandler.maybeStartDelayedKeyEvent(isA(KeyEventContext.class)); |
| keyEventHandler.sendPress('a'); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| assertTrue(keyEventContextMap.isEmpty()); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 100, 100)); |
| |
| verifyAll(); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.CENTER, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| assertTrue(view.isKeyPressed); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_release() { |
| Key targetKey = view.getKeyboard().get().getRowList().get(0).getKeyList().get(0); |
| KeyEventContext keyEventContext = |
| new KeyEventContext(targetKey, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| // The listener should receive two events. |
| // - onKey event with 'a' code. |
| // - onRelease event with 'a' code. |
| // in this order. |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| keyEventHandler.sendKey( |
| 'a', |
| Collections.singletonList(TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_UP, 25, 15, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease('a'); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, keyEventContext); |
| |
| assertEquals(Flick.Direction.CENTER, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_UP, 25, 15)); |
| |
| verifyAll(); |
| assertTrue(keyEventContextMap.isEmpty()); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_releaseInvalidKey() { |
| class TestData extends Parameter { |
| private final Key key; |
| private final int deltaX; |
| private final int deltaY; |
| TestData(Key key, int deltaX, int deltaY) { |
| this.key = key; |
| this.deltaX = deltaX; |
| this.deltaY = deltaY; |
| } |
| } |
| |
| // Center is valid, but other dictions are invalid. |
| Key validCenter = createFlickKey(0, 0, 'a', |
| KeyEntity.INVALID_KEY_CODE, |
| KeyEntity.INVALID_KEY_CODE, |
| KeyEntity.INVALID_KEY_CODE, |
| KeyEntity.INVALID_KEY_CODE); |
| |
| int delta = 100; |
| TestData[] testDataList = { |
| new TestData(createKey(0, 0, KeyEntity.INVALID_KEY_CODE), 0, 0), |
| // Try four directions, which are assigned INVALID_KEY_CODE. |
| new TestData(validCenter, -delta, 0), |
| new TestData(validCenter, +delta, 0), |
| new TestData(validCenter, 0, -delta), |
| new TestData(validCenter, 0, +delta), |
| }; |
| |
| for (TestData testData : testDataList) { |
| resetAll(); |
| |
| // Prepare a keyboard having a key with invalid key code. |
| KeyboardView view = new KeyboardView(getInstrumentation().getTargetContext()); |
| view.setKeyboard(createDummyKeyboard(testData.key)); |
| view.layout(0, 0, 100, 60); |
| |
| Key targetKey = view.getKeyboard().get().getRowList().get(0).getKeyList().get(0); |
| KeyEventContext keyEventContext = |
| new KeyEventContext(targetKey, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| // cancelDelayedKeyEvent is called by each MOVE event. |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| expectLastCall().asStub(); |
| // Pay no attention the parameter of sendRelease. |
| keyEventHandler.sendRelease(EasyMock.anyInt()); |
| // The handler's sendKey shouldn't be sent. |
| replayAll(); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, keyEventContext); |
| |
| int startX = 25; |
| int startY = 15; |
| assertTrue(testData.toString(), |
| drag(view, startX, startX + testData.deltaX, startY, startY + testData.deltaY)); |
| |
| verifyAll(); |
| assertTrue(testData.toString(), keyEventContextMap.isEmpty()); |
| } |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_releaseModified() { |
| Key key = createKeyWithModifiedState(0, 0, 'a', 'A'); |
| view.setKeyboard(createDummyKeyboard(key)); |
| Key targetKey = view.getKeyboard().get().getRowList().get(0).getKeyList().get(0); |
| KeyEventContext keyEventContext = |
| new KeyEventContext(targetKey, 0, 0, 0, 100, 60, 1, EnumSet.of(MetaState.CAPS_LOCK)); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| // The listener should receive two events. |
| // - onKey event with 'A' code. |
| // - onRelease event with 'A' code. |
| // in this order. |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| keyEventHandler.sendKey( |
| 'A', |
| Collections.singletonList(TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_UP, 25, 15, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease('A'); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| view.metaState = EnumSet.of(MetaState.CAPS_LOCK); |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, keyEventContext); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_UP, 25, 15)); |
| |
| verifyAll(); |
| assertTrue(keyEventContextMap.isEmpty()); |
| assertEquals(EnumSet.of(MetaState.CAPS_LOCK), view.metaState); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_releaseModifierSimple() { |
| Key key = createModifierKey(0, 0, -1, EnumSet.of(MetaState.SHIFT), |
| Collections.<MetaState>emptySet()); |
| view.setKeyboard(createDummyKeyboard(key)); |
| |
| KeyEventContext keyEventContext = |
| new KeyEventContext(key, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| keyEventHandler.sendKey( |
| -1, |
| Collections.singletonList(TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_UP, 25, 15, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease(-1); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| view.metaState = EnumSet.of(MetaState.SHIFT); |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| view.isKeyPressed = false; |
| keyEventContextMap.put(0, keyEventContext); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_UP, 25, 15)); |
| |
| verifyAll(); |
| assertTrue(keyEventContextMap.isEmpty()); |
| // Simple releasing a modifier key shouldn't change the metaState. |
| assertEquals(EnumSet.of(MetaState.SHIFT), view.metaState); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_releaseModifierWithOtherKey() { |
| Key key = createModifierKey(0, 0, -1, EnumSet.of(MetaState.SHIFT), |
| Collections.<MetaState>emptySet()); |
| view.setKeyboard(createDummyKeyboard(key)); |
| |
| KeyEventContext keyEventContext = |
| new KeyEventContext(key, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| // Another key event context for 'a' key. |
| KeyEventContext keyEventContext2 = |
| new KeyEventContext(createKey(0, 0, 'a'), 0, 0, 0, 100, 60, 1, |
| EnumSet.of(MetaState.SHIFT)); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| // The listener should receive two events. |
| // - onKey event with 'A' code. |
| // - onRelease event with 'A' code. |
| // in this order. |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext2); |
| keyEventHandler.sendKey( |
| 'a', |
| Arrays.asList( |
| // A TouchEvent for the SHIFT key. |
| TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_UP, 25, 15, 100, 60, 0)) |
| .build(), |
| // A TouchEvent for 'a' key. |
| TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease('a'); |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| keyEventHandler.sendKey( |
| -1, |
| Collections.singletonList(TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_UP, 25, 15, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease(-1); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| view.metaState = EnumSet.of(MetaState.SHIFT); |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, keyEventContext); |
| keyEventContextMap.put(1, keyEventContext2); |
| view.isKeyPressed = true; |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_UP, 25, 15)); |
| |
| verifyAll(); |
| assertTrue(keyEventContextMap.isEmpty()); |
| // Simple releasing a modifier key shouldn't change the metaState. |
| assertTrue(view.metaState.isEmpty()); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_releaseModifierOneTime() { |
| Key targetKey = view.getKeyboard().get().getRowList().get(0).getKeyList().get(0); |
| KeyEventContext keyEventContext = |
| new KeyEventContext(targetKey, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(keyEventContext); |
| keyEventHandler.sendKey( |
| 'a', |
| Collections.singletonList(TouchEvent.newBuilder() |
| .setSourceId(1) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_DOWN, 0, 0, 100, 60, 0)) |
| .addStroke(KeyEventContext.createTouchPosition( |
| TouchAction.TOUCH_UP, 25, 15, 100, 60, 0)) |
| .build())); |
| keyEventHandler.sendRelease('a'); |
| replayAll(); |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| view.metaState = EnumSet.of(MetaState.SHIFT); |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, keyEventContext); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_UP, 25, 15)); |
| |
| verifyAll(); |
| assertTrue(keyEventContextMap.isEmpty()); |
| // Simple releasing a modifier key shouldn't change the metaState. |
| assertTrue(view.metaState.isEmpty()); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_move() { |
| // Moving events shouldn't trigger the listener, except canceling the delayed key event. |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| replayAll(); |
| |
| // Following code emulates: |
| // - press the center of the key |
| // - move to up, which will make internal flickState to UP |
| // - move to left, which will make internal flickState to LEFT |
| // - move to down, which will make internal flickState to DOWN |
| // - move to right, which will make internal flickState to RIGHT |
| // - and, move back to the key, which will make internal flickState to CENTER. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| assertTrue(keyEventContextMap.isEmpty()); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 25, 15)); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.CENTER, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 25, -85)); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.UP, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, -75, 15)); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.LEFT, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 25, 115)); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.DOWN, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 125, 15)); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.RIGHT, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| |
| verifyAll(); |
| |
| resetAll(); |
| keyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| // Delayed key event is invoked since the next flick direction is CENTER. |
| keyEventHandler.maybeStartDelayedKeyEvent(isA(KeyEventContext.class)); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 25, 15)); |
| assertEquals(1, keyEventContextMap.size()); |
| assertEquals(Flick.Direction.CENTER, |
| keyEventContextMap.values().iterator().next().flickDirection); |
| |
| verifyAll(); |
| } |
| |
| @SmallTest |
| public void testOnTouchEvent_cancel() { |
| Key targetKey = view.getKeyboard().get().getRowList().get(0).getKeyList().get(0); |
| KeyEventContext eventContext = |
| new KeyEventContext(targetKey, 0, 0, 0, 100, 60, 1, Collections.<MetaState>emptySet()); |
| |
| KeyEventHandler keyEventHandler = createKeyEventHandlerMock(); |
| keyEventHandler.cancelDelayedKeyEvent(same(eventContext)); |
| keyEventHandler.sendCancel(); |
| replayAll(); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| // Set pressed condition. |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| keyEventContextMap.put(0, eventContext); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_CANCEL, 25, 15)); |
| assertTrue(keyEventContextMap.isEmpty()); |
| |
| verifyAll(); |
| } |
| |
| @SmallTest |
| public void testFlick() { |
| view.setKeyboard(createDummyKeyboard(createFlickKey(0, 0, 'a', 'b', 'c', 'd', 'e'))); |
| |
| view.layout(0, 0, 50, 30); |
| |
| int fromX = view.getWidth() / 2; |
| int fromY = view.getHeight() / 2; |
| |
| class TestData { |
| final int toX; |
| final int toY; |
| final int expectedCode; |
| TestData(int toX, int toY, int expectedCode) { |
| this.toX = toX; |
| this.toY = toY; |
| this.expectedCode = expectedCode; |
| } |
| } |
| TestData[] testCases = { |
| new TestData(fromX - 75, fromY, 'b'), // Left |
| new TestData(fromX + 75, fromY, 'c'), // Right |
| new TestData(fromX, fromY - 75, 'd'), // Up |
| new TestData(fromX, fromY + 75, 'e'), // Down |
| }; |
| |
| KeyboardActionListener keyboardActionListener = createStrictMock(KeyboardActionListener.class); |
| for (TestData testCase : testCases) { |
| resetAll(); |
| keyboardActionListener.onPress('a'); |
| keyboardActionListener.onKey( |
| eq(testCase.expectedCode), EasyMock.<List<TouchEvent>>notNull()); |
| keyboardActionListener.onRelease('a'); |
| replayAll(); |
| |
| KeyEventHandler keyEventHandler = |
| new KeyEventHandler(Looper.myLooper(), keyboardActionListener, 0, 0, 0); |
| |
| view.setKeyEventHandler(keyEventHandler); |
| |
| assertTrue(view.keyEventContextMap.isEmpty()); |
| assertTrue(drag(view, fromX, testCase.toX, fromY, testCase.toY)); |
| assertTrue(view.keyEventContextMap.isEmpty()); |
| |
| verifyAll(); |
| } |
| } |
| |
| @SmallTest |
| public void testFlickSensitivity() { |
| view.setKeyboard(createDummyKeyboard(createFlickKey(0, 0, 'a', 'b', 'c', 'd', 'e'))); |
| view.layout(0, 0, 50, 30); |
| |
| Map<Integer, KeyEventContext> keyEventContextMap = view.keyEventContextMap; |
| |
| int fromX = view.getWidth() / 2; |
| int fromY = view.getHeight() / 2; |
| |
| view.setFlickSensitivity(-10); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, fromX, fromY)); |
| float threshold1 = keyEventContextMap.values().iterator().next().getFlickThresholdSquared(); |
| |
| view.resetState(); |
| view.setFlickSensitivity(0); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, fromX, fromY)); |
| float threshold2 = keyEventContextMap.values().iterator().next().getFlickThresholdSquared(); |
| |
| view.resetState(); |
| view.setFlickSensitivity(10); |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, fromX, fromY)); |
| float threshold3 = keyEventContextMap.values().iterator().next().getFlickThresholdSquared(); |
| |
| assertTrue(0 < threshold3); |
| assertTrue(threshold3 <= threshold2); |
| assertTrue(threshold2 <= threshold1); |
| } |
| |
| @SmallTest |
| public void testPopUp() { |
| final Drawable icon = new ColorDrawable(); |
| final Drawable flickIcon = new ColorDrawable(); |
| final int invalidResourceId = 0; |
| final int iconResourceId = 1; |
| final int flickIconResourceId = 3; |
| |
| PopUp popup = new PopUp(iconResourceId, invalidResourceId, 40, 0, -30, 10, 10); |
| PopUp flickPopup = new PopUp(flickIconResourceId, invalidResourceId, 40, 0, -30, 10, 10); |
| |
| // Inject drawables as resources. |
| Resources mockResources = new MockResourcesWithDisplayMetrics() { |
| @Override |
| public Drawable getDrawable(int resourceId) { |
| if (resourceId == iconResourceId) { |
| return icon; |
| } |
| if (resourceId == flickIconResourceId) { |
| return flickIcon; |
| } |
| return null; |
| } |
| }; |
| DrawableCache drawableCache = new DrawableCache(mockResources); |
| VisibilityProxy.setField(view, "drawableCache", drawableCache); |
| |
| int x1 = 0; |
| int x2 = WIDTH + HORIZONTAL_GAP; |
| int x3 = (WIDTH + HORIZONTAL_GAP) * 2; |
| int y1 = 0; |
| int y2 = HEIGHT + VERTICAL_GAP; |
| int y3 = (HEIGHT + VERTICAL_GAP) * 2; |
| |
| Key popupKey = new Key( |
| x2, y2, |
| WIDTH, HEIGHT, HORIZONTAL_GAP, 0, false, false, Stick.EVEN, |
| DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND, |
| Collections.singletonList(new KeyState( |
| "", |
| Collections.<MetaState>emptySet(), |
| Collections.<MetaState>emptySet(), |
| Collections.<MetaState>emptySet(), |
| Arrays.asList( |
| new Flick( |
| Direction.CENTER, |
| new KeyEntity( |
| 1, 'a', KeyEntity.INVALID_KEY_CODE, true, 0, |
| Optional.<String>absent(), false, Optional.of(popup), 0, 0, 0, 0)), |
| new Flick( |
| Direction.LEFT, |
| new KeyEntity( |
| 2, 'b', KeyEntity.INVALID_KEY_CODE, true, 0, |
| Optional.<String>absent(), false, Optional.of(flickPopup), 0, 0, 0, 0)))))); |
| |
| Row row1 = new Row( |
| Arrays.asList(createKey(x1, y1, 'c'), createKey(x2, y1, 'd'), createKey(x3, y1, 'e')), |
| HEIGHT, VERTICAL_GAP); |
| Row row2 = new Row( |
| Arrays.asList(createKey(x1, y2, 'f'), popupKey, createKey(x3, y2, 'g')), |
| HEIGHT, VERTICAL_GAP); |
| Row row3 = new Row( |
| Arrays.asList(createKey(x1, y3, 'h'), createKey(x2, y3, 'i'), createKey(x3, y3, 'j')), |
| HEIGHT, VERTICAL_GAP); |
| Keyboard keyboard = new Keyboard(Optional.<String>absent(), |
| Arrays.asList(row1, row2, row3), 1, |
| KeyboardSpecification.TWELVE_KEY_TOGGLE_FLICK_KANA); |
| view.setKeyboard(keyboard); |
| |
| // Set mock preview. |
| PopUpPreview mockPopUpPreview = createMockBuilder(PopUpPreview.class) |
| .withConstructor(View.class, BackgroundDrawableFactory.class, DrawableCache.class) |
| .withArgs(view, new BackgroundDrawableFactory(new MockResourcesWithDisplayMetrics()), |
| drawableCache) |
| .createMock(); |
| PopUpPreview.Pool popupPreviewPool = VisibilityProxy.getField(view, "popupPreviewPool"); |
| VisibilityProxy.<List<PopUpPreview>>getField( |
| popupPreviewPool, "freeList").add(mockPopUpPreview); |
| Handler dismissHandler = new Handler(Looper.myLooper()); |
| VisibilityProxy.setField(popupPreviewPool, "dismissHandler", dismissHandler); |
| |
| // At first, emulate press event. |
| mockPopUpPreview.showIfNecessary(popupKey, Optional.of(popup), false); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 75, 45)); |
| verifyAll(); |
| |
| // Then, moving to left. |
| resetAll(); |
| mockPopUpPreview.showIfNecessary(popupKey, Optional.of(flickPopup), false); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 25, 45)); |
| verifyAll(); |
| |
| // Moving to top. |
| resetAll(); |
| mockPopUpPreview.showIfNecessary(popupKey, Optional.<PopUp>absent(), false); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 75, 15)); |
| verifyAll(); |
| |
| // Moving to center again. |
| resetAll(); |
| mockPopUpPreview.showIfNecessary(popupKey, Optional.of(popup), false); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_MOVE, 75, 45)); |
| verifyAll(); |
| |
| // Finally, release. |
| resetAll(); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_UP, 75, 45)); |
| verifyAll(); |
| |
| assertTrue(dismissHandler.hasMessages(0, mockPopUpPreview)); |
| dismissHandler.removeMessages(0); |
| |
| // If popup is disabled, no events should happen. |
| resetAll(); |
| replayAll(); |
| |
| view.setPopupEnabled(false); |
| verifyAll(); |
| |
| resetAll(); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 75, 45)); |
| verifyAll(); |
| } |
| |
| private void delayedPopupTestImpl( |
| boolean popupEnabled, boolean longPressTimeoutTrigger, boolean popupPresent, |
| boolean expectShowIfNecessaryIsCalled, boolean expectLongPressCallbackIsSet) { |
| // Set up mock drawable cache. |
| MockResources mockResources = new MockResources() { |
| @Override |
| public Drawable getDrawable(int resourceId) { |
| return new ColorDrawable(); |
| } |
| |
| @Override |
| public DisplayMetrics getDisplayMetrics() { |
| return new DisplayMetrics(); |
| } |
| }; |
| DrawableCache drawableCache = new DrawableCache(mockResources); |
| VisibilityProxy.setField(view, "drawableCache", drawableCache); |
| |
| // Set up a dummy keyboard with one key entity and mock key event handler. |
| view.setPopupEnabled(popupEnabled); |
| Optional<PopUp> popup = Optional.absent(); |
| if (popupPresent) { |
| popup = Optional.of(new PopUp(0, 0, 40, 0, -30, 10, 10)); |
| } |
| KeyEntity keyEntity = new KeyEntity( |
| 1, 'a', 'A', longPressTimeoutTrigger, 0, Optional.<String>absent(), false, popup, |
| 0, 0, 0, 0); |
| Key key = createKeyWithKeyEntity(0, 0, keyEntity); |
| view.setKeyboard(createDummyKeyboard(key)); |
| KeyEventHandler mockKeyEventHandler = createKeyEventHandlerMock(); |
| view.setKeyEventHandler(mockKeyEventHandler); |
| |
| // Set up mock popup preview. |
| PopUpPreview mockPopUpPreview = createMockBuilder(PopUpPreview.class) |
| .withConstructor(View.class, BackgroundDrawableFactory.class, DrawableCache.class) |
| .withArgs(view, new BackgroundDrawableFactory(mockResources), drawableCache) |
| .createMock(); |
| PopUpPreview.Pool popupPreviewPool = VisibilityProxy.getField(view, "popupPreviewPool"); |
| VisibilityProxy.<List<PopUpPreview>>getField( |
| popupPreviewPool, "freeList").add(mockPopUpPreview); |
| Handler dismissHandler = new Handler(Looper.myLooper()); |
| VisibilityProxy.setField(popupPreviewPool, "dismissHandler", dismissHandler); |
| |
| // Run test scenario. |
| if (expectShowIfNecessaryIsCalled) { |
| mockPopUpPreview.showIfNecessary(key, popup, false); |
| } |
| if (!popupEnabled) { |
| mockPopUpPreview.dismiss(); |
| } |
| Capture<KeyEventContext> keyEventContextCapture = new Capture<KeyEventContext>(); |
| mockKeyEventHandler.cancelDelayedKeyEvent(isA(KeyEventContext.class)); |
| expectLastCall().anyTimes(); |
| mockKeyEventHandler.maybeStartDelayedKeyEvent(capture(keyEventContextCapture)); |
| mockKeyEventHandler.sendPress('a'); |
| replayAll(); |
| |
| assertTrue(touchEvent(view, MotionEvent.ACTION_DOWN, 75, 45)); |
| verifyAll(); |
| KeyEventContext context = keyEventContextCapture.getValue(); |
| assertEquals(expectLongPressCallbackIsSet, context.longPressCallback.isPresent()); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupEnabled_Trigger_PopupIconPresent() { |
| delayedPopupTestImpl(true, true, true, true, false); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupEnabled_Trigger_PopupIconAbsent() { |
| delayedPopupTestImpl(true, true, false, true, false); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupEnabled_NotTrigger_PopupIconPresent() { |
| delayedPopupTestImpl(true, false, true, true, true); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupEnabled_NotTrigger_PopupIconAbsent() { |
| delayedPopupTestImpl(true, false, false, true, false); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupDisabled_Trigger_PopupIconPresent() { |
| delayedPopupTestImpl(false, true, true, false, false); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupDisabled_Trigger_PopupIconAbsent() { |
| delayedPopupTestImpl(false, true, false, false, false); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupDisabled_NotTrigger_PopupIconPresent() { |
| delayedPopupTestImpl(false, false, true, false, true); |
| } |
| |
| @SmallTest |
| public void testDelayedPopup_PopupDisabled_NotTrigger_PopupIconAbsent() { |
| delayedPopupTestImpl(false, false, false, false, false); |
| } |
| |
| // Unfortunately, there are no way to test multi-touch events because we can create neither |
| // MotionEvent class instances with multi-touch events nor mock instances. |
| // So, just skip the tests for those cases. |
| // TODO(hidehiko): Figure out how to write those tests, though we may need to get rid of |
| // supporting devices with lower APIs for that purpose... |
| |
| @SmallTest |
| public void testGetKeyByCoord() { |
| Key key1 = createKey(0, 0, 'a'); |
| Key key2 = createKey(WIDTH * 2, 0, 'b'); |
| |
| class TestCase { |
| final Key spacer; |
| final Key expectedLeftHalf; |
| final Key expectedRightHalf; |
| |
| TestCase(Key spacer, Key expectedLeftHalf, Key expectedRightHalf) { |
| this.spacer = spacer; |
| this.expectedLeftHalf = expectedLeftHalf; |
| this.expectedRightHalf = expectedRightHalf; |
| } |
| } |
| |
| final TestCase[] testCases = { |
| new TestCase(createSpacer(WIDTH, 0, Stick.EVEN), key1, key2), |
| new TestCase(createSpacer(WIDTH, 0, Stick.LEFT), key1, key1), |
| new TestCase(createSpacer(WIDTH, 0, Stick.RIGHT), key2, key2), |
| }; |
| |
| for (TestCase testCase : testCases) { |
| Row row = new Row(Arrays.asList(key1, testCase.spacer, key2), HEIGHT, VERTICAL_GAP); |
| view.setKeyboard(new Keyboard(Optional.<String>absent(), Collections.singletonList(row), 1, |
| KeyboardSpecification.TWELVE_KEY_TOGGLE_FLICK_KANA)); |
| |
| // Center of key1. |
| assertSame(key1, view.getKeyByCoord(WIDTH / 2, HEIGHT / 2).get()); |
| |
| // Center of key2. |
| assertSame(key2, view.getKeyByCoord(WIDTH * 5 / 2, HEIGHT / 2).get()); |
| |
| // On a spacer. |
| assertSame(testCase.expectedLeftHalf, view.getKeyByCoord(WIDTH * 4 / 3, HEIGHT / 2).get()); |
| assertSame(testCase.expectedRightHalf, view.getKeyByCoord(WIDTH * 5 / 3, HEIGHT / 2).get()); |
| |
| // Both outside of the keyboard. |
| assertSame(key1, view.getKeyByCoord(-WIDTH / 2, HEIGHT / 2).get()); |
| assertSame(key2, view.getKeyByCoord(WIDTH * 7 / 2, HEIGHT / 2).get()); |
| } |
| } |
| |
| @SmallTest |
| public void testOnDetachedFromWindow() { |
| KeyboardViewBackgroundSurface keyboardViewBackgroundSurface = |
| createKeyboardViewBackgroundSurfaceMock(); |
| keyboardViewBackgroundSurface.reset(); |
| replayAll(); |
| VisibilityProxy.setField(view, "backgroundSurface", keyboardViewBackgroundSurface); |
| |
| view.onDetachedFromWindow(); |
| |
| verifyAll(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @SmallTest |
| public void testSetSkinType() { |
| Resources resources = getInstrumentation().getTargetContext().getResources(); |
| KeyboardViewBackgroundSurface keyboardViewBackgroundSurface = |
| createKeyboardViewBackgroundSurfaceMock(); |
| keyboardViewBackgroundSurface.requestUpdateKeyboard( |
| isA(Keyboard.class), isA(Set.class)); |
| |
| DrawableCache drawableCache = createMockBuilder(DrawableCache.class) |
| .withConstructor(Resources.class) |
| .withArgs(new MockResourcesWithDisplayMetrics()) |
| .createMock(); |
| drawableCache.setSkin(SkinType.ORANGE_LIGHTGRAY.getSkin(resources)); |
| |
| BackgroundDrawableFactory backgroundDrawableFactory = |
| new BackgroundDrawableFactory(createNiceMock(MockResourcesWithDisplayMetrics.class)); |
| backgroundDrawableFactory.setSkin(SkinType.ORANGE_LIGHTGRAY.getSkin(resources)); |
| replayAll(); |
| |
| VisibilityProxy.setField(view, "backgroundSurface", keyboardViewBackgroundSurface); |
| VisibilityProxy.setField(view, "drawableCache", drawableCache); |
| VisibilityProxy.setField(view, "backgroundDrawableFactory", backgroundDrawableFactory); |
| |
| view.setSkin(SkinType.ORANGE_LIGHTGRAY.getSkin(resources)); |
| |
| verifyAll(); |
| } |
| |
| @SmallTest |
| public void testSetKeyboard() { |
| KeyboardView view = new KeyboardView(getInstrumentation().getTargetContext()); |
| Set<MetaState> originalMetaStates = EnumSet.of( |
| MetaState.NO_GLOBE, |
| MetaState.CAPS_LOCK, |
| MetaState.ACTION_GO, |
| MetaState.VARIATION_EMAIL_ADDRESS, |
| MetaState.COMPOSING); |
| view.updateMetaStates(originalMetaStates, EnumSet.noneOf(MetaState.class)); |
| assertEquals(originalMetaStates, view.getMetaStates()); |
| view.setKeyboard(new Keyboard(Optional.<String>absent(), Collections.<Row>emptyList(), 0f, |
| KeyboardSpecification.TWELVE_KEY_TOGGLE_FLICK_KANA)); |
| assertEquals( |
| EnumSet.of( |
| MetaState.NO_GLOBE, |
| MetaState.ACTION_GO, |
| MetaState.VARIATION_EMAIL_ADDRESS, |
| MetaState.COMPOSING), |
| view.getMetaStates()); |
| } |
| |
| @SmallTest |
| public void testSetEditorInfo() { |
| KeyboardView view = new KeyboardView(getInstrumentation().getTargetContext()); |
| |
| class TestData extends Parameter { |
| final int imeOptions; |
| final int inputType; |
| final Set<MetaState> expectedMetaStates; |
| TestData(int imeOptions, int inputType, Set<MetaState> expectedMetaStates) { |
| this.imeOptions = imeOptions; |
| this.inputType = inputType; |
| this.expectedMetaStates = expectedMetaStates; |
| } |
| } |
| |
| // Keep the order. KeyboardView's state is not reset in the iteration. |
| TestData[] testDataList = { |
| new TestData(0, 0, EnumSet.of(MetaState.NO_GLOBE)), |
| new TestData(EditorInfo.IME_ACTION_DONE, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_DONE)), |
| new TestData(EditorInfo.IME_ACTION_GO, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_GO)), |
| new TestData(EditorInfo.IME_ACTION_NEXT, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_NEXT)), |
| new TestData(EditorInfo.IME_ACTION_NONE, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_NONE)), |
| new TestData(EditorInfo.IME_ACTION_PREVIOUS, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_PREVIOUS)), |
| new TestData(EditorInfo.IME_ACTION_SEARCH, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_SEARCH)), |
| new TestData(EditorInfo.IME_ACTION_SEND, |
| 0, EnumSet.of(MetaState.NO_GLOBE, MetaState.ACTION_SEND)), |
| new TestData(0, InputType.TYPE_TEXT_VARIATION_URI | InputType.TYPE_CLASS_TEXT, |
| EnumSet.of(MetaState.NO_GLOBE, MetaState.VARIATION_URI)), |
| new TestData(0, InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_CLASS_TEXT, |
| EnumSet.of(MetaState.NO_GLOBE, MetaState.VARIATION_EMAIL_ADDRESS)), |
| new TestData(0, InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS | InputType.TYPE_CLASS_TEXT, |
| EnumSet.of(MetaState.NO_GLOBE, MetaState.VARIATION_EMAIL_ADDRESS)), |
| new TestData(0, InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE | InputType.TYPE_CLASS_TEXT, |
| EnumSet.of(MetaState.NO_GLOBE)), |
| new TestData(0, InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_CLASS_TEXT, |
| EnumSet.of(MetaState.NO_GLOBE, MetaState.VARIATION_EMAIL_ADDRESS)), |
| new TestData(0, InputType.TYPE_NUMBER_VARIATION_PASSWORD | InputType.TYPE_CLASS_NUMBER, |
| EnumSet.of(MetaState.NO_GLOBE)), |
| new TestData(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_ENTER_ACTION, |
| 0, EnumSet.of(MetaState.NO_GLOBE)), |
| }; |
| |
| for (TestData testData : testDataList) { |
| EditorInfo editorInfo = new EditorInfo(); |
| editorInfo.imeOptions = testData.imeOptions; |
| editorInfo.inputType = testData.inputType; |
| view.setEditorInfo(editorInfo); |
| assertEquals(testData.toString(), testData.expectedMetaStates, view.getMetaStates()); |
| } |
| } |
| } |