blob: bcae743c3ea76d6e010aa950c7ab2bf74cc08ae5 [file] [log] [blame]
// Copyright 2010-2015, 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;
import static org.mozc.android.inputmethod.japanese.testing.MozcMatcher.matchesKeyEvent;
import static org.mozc.android.inputmethod.japanese.testing.MozcMatcher.sameOptional;
import static org.easymock.EasyMock.anyBoolean;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.same;
import org.mozc.android.inputmethod.japanese.DependencyFactory.Dependency;
import org.mozc.android.inputmethod.japanese.FeedbackManager.FeedbackEvent;
import org.mozc.android.inputmethod.japanese.FeedbackManager.FeedbackListener;
import org.mozc.android.inputmethod.japanese.KeycodeConverter.KeyEventInterface;
import org.mozc.android.inputmethod.japanese.MozcService.SymbolHistoryStorageImpl;
import org.mozc.android.inputmethod.japanese.ViewManagerInterface.LayoutAdjustment;
import org.mozc.android.inputmethod.japanese.emoji.EmojiProviderType;
import org.mozc.android.inputmethod.japanese.hardwarekeyboard.HardwareKeyboard.CompositionSwitchMode;
import org.mozc.android.inputmethod.japanese.keyboard.Keyboard.KeyboardSpecification;
import org.mozc.android.inputmethod.japanese.model.JapaneseSoftwareKeyboardModel;
import org.mozc.android.inputmethod.japanese.model.SelectionTracker;
import org.mozc.android.inputmethod.japanese.model.SymbolCandidateStorage.SymbolHistoryStorage;
import org.mozc.android.inputmethod.japanese.model.SymbolMajorCategory;
import org.mozc.android.inputmethod.japanese.preference.ClientSidePreference;
import org.mozc.android.inputmethod.japanese.preference.ClientSidePreference.HardwareKeyMap;
import org.mozc.android.inputmethod.japanese.preference.ClientSidePreference.InputStyle;
import org.mozc.android.inputmethod.japanese.preference.ClientSidePreference.KeyboardLayout;
import org.mozc.android.inputmethod.japanese.preference.PreferenceUtil;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateList;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.Category;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Command;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.CompositionMode;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Context.InputFieldType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.DeletionRange;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.GenericStorageEntry.StorageType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchEvent;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Output;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Preedit;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Preedit.Segment;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Preedit.Segment.Annotation;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Request;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Result;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Result.ResultType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.SessionCommand;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoConfig.Config;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoConfig.Config.SelectionShortcut;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor.EvaluationCallback;
import org.mozc.android.inputmethod.japanese.session.SessionHandlerFactory;
import org.mozc.android.inputmethod.japanese.testing.ApiLevel;
import org.mozc.android.inputmethod.japanese.testing.InstrumentationTestCaseWithMock;
import org.mozc.android.inputmethod.japanese.testing.Parameter;
import org.mozc.android.inputmethod.japanese.ui.MenuDialog.MenuDialogListener;
import org.mozc.android.inputmethod.japanese.util.ImeSwitcherFactory.ImeSwitcher;
import org.mozc.android.inputmethod.japanese.view.Skin;
import org.mozc.android.inputmethod.japanese.view.SkinType;
import com.google.common.base.Optional;
import com.google.protobuf.ByteString;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.util.Pair;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import org.easymock.Capture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Due to limitation of AndroidMock (in more precise EasyMock 2.4 used by AndroidMock),
* It's difficult to write appropriate expectation based on IMocksControl.
* So, this test contains bad hack;
* - If the mock's state is before "replay", all arguments of method invocations are ignored,
* and default values (0, false or null) are returned.
* Thus, we abuse it as "a kind of nice mock", and invoke "reset" method before writing
* actual expectations.
* TODO(hidehiko): Remove the hack, after switching.
*
*/
public class MozcServiceTest extends InstrumentationTestCaseWithMock {
/**
* Store registered listeners to unregister them on {@code clearSharedPreferences}.
*/
private static final List<OnSharedPreferenceChangeListener> sharedPreferenceChangeListeners =
new ArrayList<OnSharedPreferenceChangeListener>(1);
private static class AlwaysShownMozcService extends MozcService {
@Override
public boolean isInputViewShown() {
return true;
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
DependencyFactory.setDependency(Optional.<Dependency>absent());
clearSharedPreferences();
}
@Override
protected void tearDown() throws Exception {
clearSharedPreferences();
DependencyFactory.setDependency(Optional.<Dependency>absent());
super.tearDown();
}
private ViewManager createViewManagerMock(Context context, ViewEventListener viewEventListener) {
return createMockBuilder(ViewManager.class)
.withConstructor(Context.class, ViewEventListener.class, SymbolHistoryStorage.class,
ImeSwitcher.class, MenuDialogListener.class)
.withArgs(context, viewEventListener, createNiceMock(SymbolHistoryStorage.class),
createNiceMock(ImeSwitcher.class), createNiceMock(MenuDialogListener.class))
.createMock();
}
// Consider using createInitializedService() instead of this method.
private MozcService createService() {
return initializeService(new MozcService());
}
private MozcService initializeService(MozcService service) {
service.sendSyncDataCommandHandler = new Handler();
service.attachBaseContext(getInstrumentation().getTargetContext());
return service;
}
private MozcService createInitializedService(SessionExecutor sessionExecutor) {
MozcService service = createService();
invokeOnCreateInternal(
service, null, getSharedPreferences(), getDefaultDeviceConfiguration(), sessionExecutor);
return service;
}
private MozcService initializeMozcService(MozcService service,
SessionExecutor sessionExecutor) {
invokeOnCreateInternal(initializeService(service), null, getSharedPreferences(),
getDefaultDeviceConfiguration(), sessionExecutor);
return service;
}
private SharedPreferences getSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getContext());
}
private void clearSharedPreferences() {
SharedPreferences sharedPreferences = getSharedPreferences();
// Clear listeners registered by {@code invokeOnCreateInternal}.
for (OnSharedPreferenceChangeListener listener : sharedPreferenceChangeListeners) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
sharedPreferenceChangeListeners.clear();
Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
}
private Configuration getDefaultDeviceConfiguration() {
Configuration configuration = new Configuration();
configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
// Note: Some other fields might be needed.
// But currently only orientation field causes flaky test results.
return configuration;
}
private static void invokeOnCreateInternal(MozcService service,
ViewManager viewManager,
SharedPreferences sharedPreferences,
Configuration deviceConfiguration,
SessionExecutor sessionExecutor) {
ViewEventListener eventListener = service.new MozcEventListener();
service.onCreateInternal(
eventListener, viewManager, sharedPreferences, deviceConfiguration, sessionExecutor);
sharedPreferenceChangeListeners.add(service.sharedPreferenceChangeListener);
}
@SmallTest
// UiThreadTest annotation to handle callback of shared preference.
@UiThreadTest
public void testOnCreate() {
SharedPreferences preferences = getSharedPreferences();
// Client-side config
preferences.edit()
.putBoolean("pref_haptic_feedback_key", true)
.putBoolean("pref_other_anonimous_mode_key", true)
.commit();
SessionExecutor sessionExecutor = createStrictMock(SessionExecutor.class);
sessionExecutor.reset(isA(SessionHandlerFactory.class), isA(Context.class));
sessionExecutor.setLogging(anyBoolean());
sessionExecutor.setImposedConfig(isA(Config.class));
sessionExecutor.setConfig(ConfigUtil.toConfig(preferences));
sessionExecutor.switchInputMode(same(Optional.<KeyEventInterface>absent()),
isA(CompositionMode.class),
anyObject(EvaluationCallback.class));
expectLastCall().asStub();
sessionExecutor.updateRequest(isA(Request.class), eq(Collections.<TouchEvent>emptyList()));
expectLastCall().asStub();
sessionExecutor.preferenceUsageStatsEvent(
anyObject(SharedPreferences.class), anyObject(Resources.class));
ViewManager viewManager = createMockBuilder(ViewManager.class)
.withConstructor(Context.class, ViewEventListener.class,
SymbolHistoryStorage.class, ImeSwitcher.class, MenuDialogListener.class)
.withArgs(getInstrumentation().getTargetContext(),
createNiceMock(ViewEventListener.class),
createNiceMock(SymbolHistoryStorage.class),
createNiceMock(ImeSwitcher.class),
createNiceMock(MenuDialogListener.class))
.addMockedMethod("onConfigurationChanged")
.createMock();
viewManager.onConfigurationChanged(anyObject(Configuration.class));
replayAll();
MozcService service = createService();
// Both sessionExecutor and viewManager is not initialized.
assertNull(service.sessionExecutor);
assertNull(service.viewManager);
// A window is not created.
assertNull(service.getWindow());
// Initialize the service.
try {
// Emulate non-dev channel situation.
MozcUtil.setDevChannel(Optional.of(false));
invokeOnCreateInternal(service, viewManager, preferences, getDefaultDeviceConfiguration(),
sessionExecutor);
} finally {
MozcUtil.setDevChannel(Optional.<Boolean>absent());
}
verifyAll();
// The following variables must have been initialized after the initialization of the service.
assertNotNull(service.getWindow());
assertSame(sessionExecutor, service.sessionExecutor);
assertNotNull(service.viewManager);
assertTrue(service.feedbackManager.isHapticFeedbackEnabled());
// SYNC_DATA should be sent periodically.
assertTrue(service.sendSyncDataCommandHandler.hasMessages(0));
// SharedPreferences should be cached.
assertSame(preferences, service.sharedPreferences);
}
@SmallTest
public void testOnCreate_viewManager() {
MozcService service = createService();
try {
invokeOnCreateInternal(service, null, getSharedPreferences(), getDefaultDeviceConfiguration(),
createNiceMock(SessionExecutor.class));
assertSame(ViewManager.class, service.viewManager.getClass());
} finally {
}
}
@SmallTest
public void testOnCreateInputView() {
MozcService service = createService();
// A test which calls onCreateInputView before onCreate is not needed
// because IMF ensures calling onCreate before onCreateInputView.
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
replayAll();
invokeOnCreateInternal(
service, null, getSharedPreferences(), getDefaultDeviceConfiguration(), sessionExecutor);
verifyAll();
assertNotNull(service.onCreateInputView());
// Created view is tested in CandidateViewTest.
}
@SmallTest
public void testKeyboardInitialized() {
MozcService service = createService();
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
KeyboardSpecification defaultSpecification = service.currentKeyboardSpecification;
sessionExecutor.updateRequest(
MozcUtil.getRequestBuilder(
service.getResources(), defaultSpecification, getDefaultDeviceConfiguration()).build(),
Collections.<TouchEvent>emptyList());
replayAll();
invokeOnCreateInternal(
service, null, getSharedPreferences(), getDefaultDeviceConfiguration(), sessionExecutor);
verifyAll();
}
@SmallTest
public void testOnFinishInput() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
resetAll();
sessionExecutor.resetContext();
ViewEventListener eventListener = createNiceMock(ViewEventListener.class);
Context context = getInstrumentation().getTargetContext();
ViewManager viewManager = createViewManagerMock(context, eventListener);
service.viewManager = viewManager;
viewManager.reset();
replayAll();
// When onFinishInput is called, the inputHandler should receive CLEANUP command.
service.onFinishInput();
verifyAll();
}
@SmallTest
public void testOnStartInput_notRestarting() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
MozcService service = createInitializedService(sessionExecutor);
Capture<Request> requestCapture = new Capture<Request>();
resetAll();
sessionExecutor.resetContext();
// Exactly one message is issued to switch input field type by onStartInput().
// It should not render a result in order to preserve
// the existent text. b/4422455
sessionExecutor.switchInputFieldType(InputFieldType.NORMAL);
// Setting for Emoji attributes.
sessionExecutor.updateRequest(
capture(requestCapture), eq(Collections.<TouchEvent>emptyList()));
selectionTracker.onStartInput(1, 2, false);
replayAll();
service.selectionTracker = selectionTracker;
EditorInfo editorInfo = new EditorInfo();
editorInfo.initialSelStart = 1;
editorInfo.initialSelEnd = 2;
service.onStartInput(editorInfo, false);
verifyAll();
assertTrue(requestCapture.getValue().hasEmojiRewriterCapability());
assertTrue(requestCapture.getValue().hasAvailableEmojiCarrier());
}
@SmallTest
public void testOnStartInput_restarting() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
MozcService service = createInitializedService(sessionExecutor);
service.selectionTracker = selectionTracker;
Capture<Request> requestCapture = new Capture<Request>();
resetAll();
sessionExecutor.resetContext();
// Exactly one message is issued to switch input field type by onStartInput().
// It should not render a result in order to preserve
// the existent text. b/4422455
sessionExecutor.switchInputFieldType(InputFieldType.NORMAL);
// Setting for Emoji attributes.
sessionExecutor.updateRequest(
capture(requestCapture), eq(Collections.<TouchEvent>emptyList()));
selectionTracker.onStartInput(1, 2, false);
replayAll();
EditorInfo editorInfo = new EditorInfo();
editorInfo.initialSelStart = 1;
editorInfo.initialSelEnd = 2;
service.onStartInput(editorInfo, true);
verifyAll();
assertTrue(requestCapture.getValue().hasEmojiRewriterCapability());
assertTrue(requestCapture.getValue().hasAvailableEmojiCarrier());
}
@SmallTest
public void testOnCompositionModeChange() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
service.currentKeyboardSpecification = KeyboardSpecification.TWELVE_KEY_TOGGLE_ALPHABET;
resetAll();
sessionExecutor.updateRequest(anyObject(ProtoCommands.Request.class),
eq(Collections.<TouchEvent>emptyList()));
// Exactly one message is issued to switch composition mode.
// It should not render a result in order to preserve
// the existent text. b/5181946
sessionExecutor.switchInputMode(same(Optional.<KeyEventInterface>absent()),
eq(CompositionMode.HIRAGANA),
anyObject(EvaluationCallback.class));
replayAll();
ViewEventListener listener = service.new MozcEventListener();
listener.onKeyEvent(null, null, KeyboardSpecification.TWELVE_KEY_TOGGLE_KANA,
Collections.<TouchEvent>emptyList());
verifyAll();
}
@SmallTest
public void testSendKeyEventBackToApplication() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
resetAll();
sessionExecutor.sendKeyEvent(isA(KeyEventInterface.class), isA(EvaluationCallback.class));
replayAll();
ViewEventListener listener = service.new MozcEventListener();
listener.onKeyEvent(null, KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_BACK), null,
Collections.<TouchEvent>emptyList());
verifyAll();
}
@SmallTest
public void testOnKeyUp() {
SessionExecutor sessionExecutor = createMockBuilder(SessionExecutor.class)
.addMockedMethod("sendKeyEvent")
.createMock();
replayAll();
MozcService service = initializeMozcService(new AlwaysShownMozcService(), sessionExecutor);
KeyEvent[] keyEventsPassedToCallback = {
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK),
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT),
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_RIGHT),
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ALT_LEFT),
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ALT_RIGHT),
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_CTRL_LEFT),
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_CTRL_RIGHT),
};
for (KeyEvent keyEvent : keyEventsPassedToCallback) {
resetAll();
sessionExecutor.sendKeyEvent(matchesKeyEvent(keyEvent), isA(EvaluationCallback.class));
replayAll();
assertTrue(service.onKeyUp(keyEvent.getKeyCode(), keyEvent));
verifyAll();
}
}
@SmallTest
// UiThreadTest annotation to handle callback of shared preference.
@UiThreadTest
public void testOnKeyDownByHardwareKeyboard() {
Context context = getInstrumentation().getTargetContext();
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("isInputViewShown", "sendKeyWithKeyboardSpecification")
.createMock();
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
ViewEventListener eventListener = createNiceMock(ViewEventListener.class);
ViewManager viewManager = createMockBuilder(ViewManager.class)
.withConstructor(Context.class, ViewEventListener.class,
SymbolHistoryStorage.class, ImeSwitcher.class, MenuDialogListener.class)
.withArgs(context, eventListener, createNiceMock(SymbolHistoryStorage.class),
createNiceMock(ImeSwitcher.class), createNiceMock(MenuDialogListener.class))
.addMockedMethods("onHardwareKeyEvent")
.createNiceMock();
service.sendKeyWithKeyboardSpecification(
same(ProtoCommands.KeyEvent.class.cast(null)), same(KeyEventInterface.class.cast(null)),
anyObject(KeyboardSpecification.class), anyObject(Configuration.class),
eq(Collections.<TouchEvent>emptyList()));
expectLastCall().asStub();
// Before test, we need to setup mock instances with some method calls.
// Because the calls is out of this test, we do it by NiceMock and replayAll.
replayAll();
initializeMozcService(service, sessionExecutor);
SharedPreferences sharedPreferences = getSharedPreferences();
invokeOnCreateInternal(service, viewManager, sharedPreferences,
context.getResources().getConfiguration(), sessionExecutor);
sharedPreferences.edit().remove(PreferenceUtil.PREF_HARDWARE_KEYMAP).commit();
verifyAll();
// Real test part.
resetAll();
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A);
expect(service.isInputViewShown()).andStubReturn(true);
ClientSidePreference clientSidePreference = service.propagatedClientSidePreference;
assertNotNull(clientSidePreference);
assertEquals(HardwareKeyMap.DEFAULT, clientSidePreference.getHardwareKeyMap());
viewManager.onHardwareKeyEvent(event);
replayAll();
assertTrue(service.onKeyDownInternal(0, event, getDefaultDeviceConfiguration()));
verifyAll();
}
@SmallTest
public void testSendKeyWithKeyboardSpecification_switchKeyboardSpecification() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
EvaluationCallback renderResultCallback = service.renderResultCallback;
service.currentKeyboardSpecification = KeyboardSpecification.TWELVE_KEY_FLICK_KANA;
// Send "a" without keyboard specification change. [TWELVE_KEY_FLICK_KANA]
resetAll();
ProtoCommands.KeyEvent mozcKeyEvent =
ProtoCommands.KeyEvent.newBuilder().setKeyCode('a').build();
KeyEventInterface keyEvent = KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A);
KeyboardSpecification keyboardSpecification = KeyboardSpecification.TWELVE_KEY_FLICK_KANA;
sessionExecutor.sendKey(
mozcKeyEvent, keyEvent, Collections.<TouchEvent>emptyList(), renderResultCallback);
replayAll();
service.sendKeyWithKeyboardSpecification(
mozcKeyEvent, keyEvent, keyboardSpecification, getDefaultDeviceConfiguration(),
Collections.<TouchEvent>emptyList());
verifyAll();
// Send "a" with keyboard specification change. [HARDWARE_QWERTY_KANA]
// This is transition from software to hardware keyboard, so submit() is called.
resetAll();
mozcKeyEvent = ProtoCommands.KeyEvent.newBuilder().setKeyCode('a').build();
keyEvent = KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A);
keyboardSpecification = KeyboardSpecification.HARDWARE_QWERTY_KANA;
sessionExecutor.submit(same(service.renderResultCallback));
sessionExecutor.updateRequest(
MozcUtil.getRequestBuilder(
service.getResources(), keyboardSpecification, getDefaultDeviceConfiguration()).build(),
Collections.<TouchEvent>emptyList());
sessionExecutor.sendKey(
ProtoCommands.KeyEvent.newBuilder(mozcKeyEvent)
.setMode(keyboardSpecification.getCompositionMode())
.build(),
keyEvent, Collections.<TouchEvent>emptyList(), renderResultCallback);
replayAll();
service.sendKeyWithKeyboardSpecification(
mozcKeyEvent, keyEvent, keyboardSpecification, getDefaultDeviceConfiguration(),
Collections.<TouchEvent>emptyList());
verifyAll();
// Send "a" without keyboard specification change. [HARDWARE_QWERTY_KANA]
resetAll();
mozcKeyEvent = ProtoCommands.KeyEvent.newBuilder().setKeyCode('a').build();
keyEvent = KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A);
keyboardSpecification = KeyboardSpecification.HARDWARE_QWERTY_KANA;
sessionExecutor.sendKey(
mozcKeyEvent, keyEvent, Collections.<TouchEvent>emptyList(), renderResultCallback);
replayAll();
service.sendKeyWithKeyboardSpecification(
mozcKeyEvent, keyEvent, keyboardSpecification, getDefaultDeviceConfiguration(),
Collections.<TouchEvent>emptyList());
verifyAll();
// Change keyboard specification. [TWELVE_KEY_FLICK_KANA]
resetAll();
mozcKeyEvent = null;
keyEvent = null;
keyboardSpecification = KeyboardSpecification.TWELVE_KEY_FLICK_KANA;
sessionExecutor.updateRequest(
MozcUtil.getRequestBuilder(
service.getResources(), keyboardSpecification, getDefaultDeviceConfiguration()).build(),
Collections.<TouchEvent>emptyList());
sessionExecutor.switchInputMode(same(Optional.<KeyEventInterface>absent()),
eq(keyboardSpecification.getCompositionMode()),
same(renderResultCallback));
replayAll();
service.sendKeyWithKeyboardSpecification(
mozcKeyEvent, keyEvent, keyboardSpecification, getDefaultDeviceConfiguration(),
Collections.<TouchEvent>emptyList());
verifyAll();
}
@SmallTest
public void testMetaKeyHandling_b13238551() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
service.currentKeyboardSpecification = KeyboardSpecification.TWELVE_KEY_FLICK_KANA;
ProtoCommands.KeyEvent mozcKeyEvent = null;
KeyEventInterface keyEvent = KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_SHIFT_LEFT);
// Send shift key only without keyboard specification change.
resetAll();
KeyboardSpecification keyboardSpecification = KeyboardSpecification.TWELVE_KEY_FLICK_KANA;
sessionExecutor.sendKeyEvent(keyEvent, service.sendKeyToApplicationCallback);
replayAll();
service.sendKeyWithKeyboardSpecification(
mozcKeyEvent, keyEvent, keyboardSpecification, getDefaultDeviceConfiguration(),
Collections.<TouchEvent>emptyList());
verifyAll();
// Send shift key only with keyboard specification change.
resetAll();
keyboardSpecification = KeyboardSpecification.HARDWARE_QWERTY_KANA;
sessionExecutor.submit(same(service.renderResultCallback));
sessionExecutor.updateRequest(
MozcUtil.getRequestBuilder(
service.getResources(), keyboardSpecification, getDefaultDeviceConfiguration()).build(),
Collections.<TouchEvent>emptyList());
sessionExecutor.switchInputMode(
sameOptional(keyEvent), isA(CompositionMode.class), same(service.renderResultCallback));
replayAll();
service.sendKeyWithKeyboardSpecification(
mozcKeyEvent, keyEvent, keyboardSpecification, getDefaultDeviceConfiguration(),
Collections.<TouchEvent>emptyList());
verifyAll();
// Handle meta key on renderInputConnection invoked by renderResultCallback.
resetAll();
service.sendKeyEvent(keyEvent);
replayAll();
service.renderInputConnection(
Command.newBuilder()
.setInput(Input.newBuilder()
.setType(Input.CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.SWITCH_INPUT_MODE)))
.setOutput(Output.newBuilder()
.setConsumed(true))
.build(),
keyEvent);
verifyAll();
}
@SmallTest
public void testShowWindow() {
final InputConnection inputConnection = createMock(InputConnection.class);
expect(inputConnection.beginBatchEdit()).andStubReturn(true);
expect(inputConnection.endBatchEdit()).andStubReturn(true);
// Expect that setComposingText() is not called.
// If it is called, when the IME is shown existing selection range is cleared unexpectedly.
// Unfortunately EasyMock#times() methods doesn't accept parameter 0 (meaning that
// mocked method shouldn't be called) so such indirect approach is employed.
replayAll();
MozcService service = new MozcService() {
@Override
public InputConnection getCurrentInputConnection() {
return inputConnection;
}
};
service.attachBaseContext(getInstrumentation().getTargetContext());
DependencyFactory.setDependency(Optional.of(DependencyFactory.TOUCH_FRAGMENT_PREF));
service.onCreate();
service.onWindowShown();
service.sessionExecutor.waitForAllQueuesForEmpty();
verifyAll();
}
@SmallTest
public void testHideWindow() {
SessionExecutor sessionExecutor = createStrictMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
InputConnection inputConnection = createMock(InputConnection.class);
// restartInput() can be used to install our InputConnection safely.
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
// When the IME is binded to certain view and its window has been shown by showWindow(),
// "finishComposingText" is called from InputMethodService::hideWindow() so that
// the preedit is submitted.
// However "finishComposingText" is not called in this test case because
// the inputConnection is not binded to a view.
resetAll();
sessionExecutor.removePendingEvaluations();
sessionExecutor.resetContext();
replayAll();
service.onWindowHidden();
verifyAll();
}
@SmallTest
public void testRenderInputConnection_updatingPreedit() {
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
service.selectionTracker = selectionTracker;
InputConnection inputConnection = createStrictMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
Preedit preedit = Preedit.newBuilder()
.addSegment(Segment.newBuilder()
.setValue("\uD83D\uDC31") // a character meaning a cat
.setAnnotation(Annotation.HIGHLIGHT)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("1")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("2")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.setCursor(1)
.build();
Command command = Command.newBuilder()
.setInput(Input.newBuilder().setType(Input.CommandType.SEND_KEY))
.setOutput(Output.newBuilder()
.setConsumed(true).setPreedit(preedit)
.setAllCandidateWords(CandidateList.newBuilder().setCategory(Category.SUGGESTION)))
.build();
resetAll();
expect(inputConnection.beginBatchEdit()).andReturn(true);
Capture<SpannableStringBuilder> spannableStringBuilderCapture =
new Capture<SpannableStringBuilder>();
// the cursor is at the tail
expect(inputConnection.setComposingText(capture(spannableStringBuilderCapture), eq(1)))
.andReturn(true);
expect(selectionTracker.getPreeditStartPosition()).andReturn(0);
expect(inputConnection.setSelection(1, 1)).andReturn(true);
selectionTracker.onRender(null, null, preedit);
expect(inputConnection.endBatchEdit()).andReturn(true);
replayAll();
service.renderInputConnection(command, KeycodeConverter.getKeyEventInterface('\0'));
verifyAll();
SpannableStringBuilder sb = spannableStringBuilderCapture.getValue();
assertEquals("\uD83D\uDC3112", sb.toString());
assertEquals(0, sb.getSpanStart(MozcService.SPAN_UNDERLINE));
assertEquals(4, sb.getSpanEnd(MozcService.SPAN_UNDERLINE));
assertEquals(2, sb.getSpanStart(MozcService.SPAN_PARTIAL_SUGGESTION_COLOR));
assertEquals(4, sb.getSpanEnd(MozcService.SPAN_PARTIAL_SUGGESTION_COLOR));
assertEquals(0, sb.getSpanStart(MozcService.SPAN_BEFORE_CURSOR));
assertEquals(2, sb.getSpanEnd(MozcService.SPAN_BEFORE_CURSOR));
}
@SmallTest
public void testRenderInputConnection_updatingPreeditAtCursorMiddle() {
// Updating preedit (cursor is at the middle)
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
service.selectionTracker = selectionTracker;
InputConnection inputConnection = createStrictMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
Preedit preedit = Preedit.newBuilder()
.addSegment(Segment.newBuilder()
.setValue("0")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("1")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("2")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.setCursor(2)
.build();
Command command = Command.newBuilder()
.setInput(Input.newBuilder().setType(Input.CommandType.SEND_KEY))
.setOutput(Output.newBuilder().setConsumed(true).setPreedit(preedit))
.build();
resetAll();
expect(inputConnection.beginBatchEdit()).andReturn(true);
Capture<SpannableStringBuilder> spannableStringBuilderCapture =
new Capture<SpannableStringBuilder>();
expect(inputConnection.setComposingText(
capture(spannableStringBuilderCapture), eq(MozcUtil.CURSOR_POSITION_TAIL)))
.andReturn(true);
expect(selectionTracker.getPreeditStartPosition()).andReturn(0);
expect(inputConnection.setSelection(2, 2)).andReturn(true);
selectionTracker.onRender(null, null, preedit);
expect(inputConnection.endBatchEdit()).andReturn(true);
replayAll();
service.renderInputConnection(command, KeycodeConverter.getKeyEventInterface('\0'));
verifyAll();
SpannableStringBuilder sb = spannableStringBuilderCapture.getValue();
assertEquals("012", sb.toString());
assertEquals(0, sb.getSpanStart(MozcService.SPAN_UNDERLINE));
assertEquals(3, sb.getSpanEnd(MozcService.SPAN_UNDERLINE));
assertEquals(0, sb.getSpanStart(MozcService.SPAN_BEFORE_CURSOR));
assertEquals(2, sb.getSpanEnd(MozcService.SPAN_BEFORE_CURSOR));
}
@SmallTest
public void testRenderInputConnection_webView() {
// Updating preedit (cursor is at the middle)
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
MozcService service = createInitializedService(sessionExecutor);
InputConnection inputConnection = createStrictMock(InputConnection.class);
EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
service.onCreateInputMethodInterface().restartInput(inputConnection, editorInfo);
Preedit preedit = Preedit.newBuilder()
.addSegment(Segment.newBuilder()
.setValue("0")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("1")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("2")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.setCursor(2)
.build();
resetAll();
expect(inputConnection.beginBatchEdit()).andReturn(true);
Capture<SpannableStringBuilder> spannableStringBuilderCapture =
new Capture<SpannableStringBuilder>();
expect(inputConnection.setComposingText(
capture(spannableStringBuilderCapture), eq(MozcUtil.CURSOR_POSITION_TAIL)))
.andReturn(true);
expect(selectionTracker.getPreeditStartPosition()).andReturn(0);
expect(inputConnection.setSelection(2, 2)).andReturn(true);
selectionTracker.onRender(null, null, preedit);
expect(inputConnection.endBatchEdit()).andReturn(true);
replayAll();
service.selectionTracker = selectionTracker;
Command command = Command.newBuilder()
.setInput(Input.newBuilder().setType(Input.CommandType.SEND_KEY))
.setOutput(Output.newBuilder().setConsumed(true).setPreedit(preedit))
.build();
service.renderInputConnection(command, KeycodeConverter.getKeyEventInterface('\0'));
verifyAll();
SpannableStringBuilder sb = spannableStringBuilderCapture.getValue();
assertEquals("012", sb.toString());
assertEquals(0, sb.getSpanStart(MozcService.SPAN_UNDERLINE));
assertEquals(3, sb.getSpanEnd(MozcService.SPAN_UNDERLINE));
assertEquals(0, sb.getSpanStart(MozcService.SPAN_BEFORE_CURSOR));
assertEquals(2, sb.getSpanEnd(MozcService.SPAN_BEFORE_CURSOR));
}
@SmallTest
public void testRenderInputConnection_commit() {
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
service.selectionTracker = selectionTracker;
InputConnection inputConnection = createStrictMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
Preedit preedit = Preedit.newBuilder()
.addSegment(Segment.newBuilder()
.setValue("0")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("1")
.setAnnotation(Annotation.UNDERLINE)
.setValueLength(1))
.addSegment(Segment.newBuilder()
.setValue("2")
.setAnnotation(Annotation.HIGHLIGHT)
.setValueLength(1))
.setCursor(3)
.build();
Command command = Command.newBuilder()
.setInput(Input.newBuilder().setType(Input.CommandType.SEND_KEY))
.setOutput(Output.newBuilder()
.setConsumed(true)
.setResult(Result.newBuilder().setValue("commit").setType(ResultType.STRING))
.setPreedit(preedit)
.setAllCandidateWords(CandidateList.newBuilder().setCategory(Category.CONVERSION)))
.build();
resetAll();
expect(inputConnection.beginBatchEdit()).andReturn(true);
expect(inputConnection.commitText("commit", MozcUtil.CURSOR_POSITION_TAIL)).andReturn(true);
Capture<SpannableStringBuilder> spannableStringBuilderCapture =
new Capture<SpannableStringBuilder>();
expect(inputConnection.setComposingText(
capture(spannableStringBuilderCapture), eq(MozcUtil.CURSOR_POSITION_TAIL)))
.andReturn(true);
selectionTracker.onRender(null, "commit", preedit);
expect(inputConnection.endBatchEdit()).andReturn(true);
replayAll();
service.renderInputConnection(command, KeycodeConverter.getKeyEventInterface('\0'));
verifyAll();
SpannableStringBuilder sb = spannableStringBuilderCapture.getValue();
assertEquals("012", sb.toString());
assertEquals(0, sb.getSpanStart(MozcService.SPAN_UNDERLINE));
assertEquals(3, sb.getSpanEnd(MozcService.SPAN_UNDERLINE));
assertEquals(2, sb.getSpanStart(MozcService.SPAN_CONVERT_HIGHLIGHT));
assertEquals(3, sb.getSpanEnd(MozcService.SPAN_CONVERT_HIGHLIGHT));
}
@SmallTest
public void testRenderInputConnection_directInput() {
// If mozc service doesn't consume the KeyEvent, delegate it to the sendKeyEvent.
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("sendKeyEvent")
.createMock();
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
initializeMozcService(service, sessionExecutor);
InputConnection inputConnection = createStrictMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
KeyEventInterface keyEvent = KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A);
resetAll();
service.sendKeyEvent(same(keyEvent));
replayAll();
Command command = Command.newBuilder()
.setInput(Input.newBuilder().setType(Input.CommandType.SEND_KEY))
.setOutput(Output.newBuilder().setConsumed(false))
.build();
service.renderInputConnection(command, keyEvent);
verifyAll();
}
/**
* If the output is for SWITCH_INPUT_MODE, composing text of InputConnection
* must not be updated by setComposingText.
*/
@SmallTest
public void testRenderInputConnection_switchInputMode() {
MozcService service = createInitializedService(createNiceMock(SessionExecutor.class));
InputConnection inputConnection =
new InputConnectionWrapper(createNiceMock(InputConnection.class), false) {
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
fail("setComposingText shouldn't be called for SWITCH_INPUT_MODE");
return true;
}
};
replayAll();
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
Command command = Command.newBuilder()
.setInput(Input.newBuilder()
.setType(Input.CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.SWITCH_INPUT_MODE)))
.setOutput(Output.newBuilder().setConsumed(true))
.build();
service.renderInputConnection(command, null);
}
@SmallTest
public void testSendKeyEvent() {
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("requestHideSelf", "sendDownUpKeyEvents", "isInputViewShown",
"sendDefaultEditorAction", "getCurrentInputConnection")
.createMock();
ViewEventListener eventListener = createNiceMock(ViewEventListener.class);
ViewManager viewManager =
createViewManagerMock(getInstrumentation().getTargetContext(), eventListener);
service.viewManager = viewManager;
// If the given KeyEvent is null, do nothing.
resetAll();
replayAll();
service.sendKeyEvent(null);
verifyAll();
// If the keyEvent is other than back key or enter key,
// just forward it to the connected application.
resetAll();
expect(service.getCurrentInputConnection()).andStubReturn(createMock(InputConnection.class));
service.sendDownUpKeyEvents(KeyEvent.KEYCODE_A);
replayAll();
service.sendKeyEvent(KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A));
verifyAll();
// For meta keys, captured once.
{
resetAll();
InputConnection inputConnection = createMock(InputConnection.class);
expect(service.getCurrentInputConnection()).andStubReturn(inputConnection);
Capture<KeyEvent> captureKeyEvent = new Capture<KeyEvent>();
expect(inputConnection.sendKeyEvent(capture(captureKeyEvent))).andReturn(true);
KeyEvent keyEvent = new KeyEvent(
MozcUtil.getUptimeMillis(),
MozcUtil.getUptimeMillis(),
KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_SHIFT_LEFT,
3,
KeyEvent.META_SHIFT_ON,
5,
0xff,
KeyEvent.FLAG_LONG_PRESS);
replayAll();
service.sendKeyEvent(KeycodeConverter.getKeyEventInterface(keyEvent));
verifyAll();
KeyEvent captured = captureKeyEvent.getValue();
assertEquals(keyEvent.getDownTime(), captured.getDownTime());
// Sent KeyEvent should have event time delayed to original KeyEvent.
assertTrue(keyEvent.getEventTime() <= captured.getEventTime());
assertEquals(keyEvent.getAction(), captured.getAction());
assertEquals(keyEvent.getKeyCode(), captured.getKeyCode());
assertEquals(keyEvent.getRepeatCount(), captured.getRepeatCount());
assertEquals(keyEvent.getMetaState(), captured.getMetaState());
assertEquals(keyEvent.getDeviceId(), captured.getDeviceId());
assertEquals(keyEvent.getScanCode(), captured.getScanCode());
assertEquals(keyEvent.getFlags(), captured.getFlags());
}
// For other keys, captured twice.
{
resetAll();
InputConnection inputConnection = createStrictMock(InputConnection.class);
expect(service.getCurrentInputConnection()).andStubReturn(inputConnection);
Capture<KeyEvent> captureKeyEventDown = new Capture<KeyEvent>();
Capture<KeyEvent> captureKeyEventUp = new Capture<KeyEvent>();
expect(inputConnection.sendKeyEvent(capture(captureKeyEventDown))).andReturn(true);
expect(inputConnection.sendKeyEvent(capture(captureKeyEventUp))).andReturn(true);
KeyEvent keyEvent = new KeyEvent(
MozcUtil.getUptimeMillis(),
MozcUtil.getUptimeMillis(),
KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_A,
3,
KeyEvent.META_SHIFT_ON,
5,
0xff,
KeyEvent.FLAG_LONG_PRESS);
replayAll();
service.sendKeyEvent(KeycodeConverter.getKeyEventInterface(keyEvent));
verifyAll();
KeyEvent capturedDown = captureKeyEventDown.getValue();
assertEquals(keyEvent.getDownTime(), capturedDown.getDownTime());
// Sent KeyEvent should have event time delayed to original KeyEvent.
assertTrue(keyEvent.getEventTime() <= capturedDown.getEventTime());
assertEquals(KeyEvent.ACTION_DOWN, capturedDown.getAction());
assertEquals(keyEvent.getKeyCode(), capturedDown.getKeyCode());
assertEquals(0, capturedDown.getRepeatCount());
assertEquals(keyEvent.getMetaState(), capturedDown.getMetaState());
assertEquals(keyEvent.getDeviceId(), capturedDown.getDeviceId());
assertEquals(keyEvent.getScanCode(), capturedDown.getScanCode());
assertEquals(keyEvent.getFlags(), capturedDown.getFlags());
KeyEvent capturedUp = captureKeyEventUp.getValue();
assertEquals(keyEvent.getDownTime(), capturedUp.getDownTime());
// Sent KeyEvent should have event time delayed to original KeyEvent.
assertTrue(keyEvent.getEventTime() <= capturedUp.getEventTime());
assertEquals(KeyEvent.ACTION_UP, capturedUp.getAction());
assertEquals(keyEvent.getKeyCode(), capturedUp.getKeyCode());
assertEquals(0, capturedUp.getRepeatCount());
assertEquals(keyEvent.getMetaState(), capturedUp.getMetaState());
assertEquals(keyEvent.getDeviceId(), capturedUp.getDeviceId());
assertEquals(keyEvent.getScanCode(), capturedUp.getScanCode());
assertEquals(keyEvent.getFlags(), capturedUp.getFlags());
}
}
@SmallTest
public void testSendKeyEventBack() {
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("requestHideSelf", "sendDownUpKeyEvents", "isInputViewShown",
"sendDefaultEditorAction")
.createMock();
ViewEventListener eventListener = createNiceMock(ViewEventListener.class);
ViewManager viewManager =
createViewManagerMock(getInstrumentation().getTargetContext(), eventListener);
service.viewManager = viewManager;
KeyEventInterface[] keyEventList = {
// Software key event.
KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_BACK),
// Hardware key event.
KeycodeConverter.getKeyEventInterface(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_BACK)),
};
for (KeyEventInterface keyEvent : keyEventList) {
// Test for back key.
// If the input view is not shown, don't handle inside the mozc, and pass it through to the
// connected application.
resetAll();
expect(service.isInputViewShown()).andReturn(false);
service.sendDownUpKeyEvents(KeyEvent.KEYCODE_BACK);
replayAll();
service.sendKeyEvent(keyEvent);
verifyAll();
// If SymbolInputView is shown, close it and do not pass the event
// to the client application.
resetAll();
expect(service.isInputViewShown()).andReturn(true);
expect(viewManager.hideSubInputView()).andReturn(true);
replayAll();
service.sendKeyEvent(keyEvent);
verifyAll();
// If the main view (software keyboard) is shown, close it and do nto pass the event to the
// client application.
resetAll();
expect(service.isInputViewShown()).andReturn(true);
expect(viewManager.hideSubInputView()).andReturn(false);
service.requestHideSelf(0);
replayAll();
service.sendKeyEvent(keyEvent);
verifyAll();
}
}
@SmallTest
public void testSendKeyEventEnter() {
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("requestHideSelf", "sendDownUpKeyEvents", "isInputViewShown",
"sendDefaultEditorAction", "getCurrentInputEditorInfo")
.createMock();
ViewEventListener eventListener = createNiceMock(ViewEventListener.class);
ViewManager viewManager =
createViewManagerMock(getInstrumentation().getTargetContext(), eventListener);
service.viewManager = viewManager;
KeyEventInterface[] keyEventList = {
// Software key event.
KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_ENTER),
// Hardware key event.
KeycodeConverter.getKeyEventInterface(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_ENTER)),
};
for (KeyEventInterface keyEvent : keyEventList) {
// Test for enter key.
// If the input view is not shown, don't handle inside the mozc.
resetAll();
expect(service.isInputViewShown()).andReturn(false);
service.sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
replayAll();
service.sendKeyEvent(keyEvent);
verifyAll();
// If the not-consumed key event is enter, fallback to default editor action.
resetAll();
expect(service.isInputViewShown()).andReturn(true);
expect(service.sendDefaultEditorAction(true)).andReturn(true);
expect(service.getCurrentInputEditorInfo()).andStubReturn(new EditorInfo());
replayAll();
service.sendKeyEvent(keyEvent);
verifyAll();
}
}
@SmallTest
public void testRenderInputConnection_directInputDeletionRange() {
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
service.selectionTracker = selectionTracker;
InputConnection inputConnection = createStrictMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
class TestData extends Parameter {
final DeletionRange deletionRange;
final Pair<Integer, Integer> expectedRange;
TestData(DeletionRange deletionRange, Pair<Integer, Integer> expectedRange) {
this.deletionRange = deletionRange;
this.expectedRange = expectedRange;
}
}
TestData [] testDataList = {
new TestData(
DeletionRange.newBuilder().setOffset(-3).setLength(3).build(), Pair.create(3, 0)),
new TestData(
DeletionRange.newBuilder().setOffset(0).setLength(3).build(), Pair.create(0, 3)),
new TestData(
DeletionRange.newBuilder().setOffset(-3).setLength(6).build(), Pair.create(3, 3)),
new TestData(
DeletionRange.newBuilder().setOffset(0).setLength(0).build(), Pair.create(0, 0)),
new TestData(DeletionRange.newBuilder().setOffset(-3).setLength(2).build(), null),
new TestData(DeletionRange.newBuilder().setOffset(1).setLength(2).build(), null),
};
for (TestData testData : testDataList) {
resetAll();
expect(inputConnection.beginBatchEdit()).andReturn(true);
if (testData.expectedRange != null) {
expect(inputConnection.deleteSurroundingText(
testData.expectedRange.first, testData.expectedRange.second))
.andReturn(true);
}
Capture<CharSequence> composingTextCapture = new Capture<CharSequence>();
expect(inputConnection.setComposingText(capture(composingTextCapture), eq(0)))
.andReturn(true);
selectionTracker.onRender(testData.deletionRange, null, null);
expect(inputConnection.endBatchEdit()).andReturn(true);
replayAll();
Command command = Command.newBuilder()
.setInput(Input.newBuilder().setType(Input.CommandType.SEND_KEY))
.setOutput(Output.newBuilder()
.setConsumed(true)
.setDeletionRange(testData.deletionRange))
.build();
service.renderInputConnection(command, null);
verifyAll();
assertEquals("", composingTextCapture.getValue().toString());
}
}
@SmallTest
public void testSwitchKeyboard() throws SecurityException, IllegalArgumentException {
// Prepare the service.
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
// Prepares the mock.
resetAll();
List<TouchEvent> touchEventList = Collections.singletonList(TouchEvent.getDefaultInstance());
KeyboardSpecification newSpecification = KeyboardSpecification.QWERTY_KANA;
sessionExecutor.updateRequest(
MozcUtil.getRequestBuilder(
service.getResources(), newSpecification, getDefaultDeviceConfiguration()).build(),
touchEventList);
sessionExecutor.switchInputMode(same(Optional.<KeyEventInterface>absent()),
eq(newSpecification.getCompositionMode()),
anyObject(EvaluationCallback.class));
replayAll();
// Get the event listener and execute.
ViewEventListener listener = service.new MozcEventListener();
{
// To stabilize the unittest, overwrite orientation and write back once after the invocation.
Configuration backup = new Configuration(service.getResources().getConfiguration());
try {
service.getResources().getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
listener.onKeyEvent(null, null, newSpecification, touchEventList);
} finally {
service.getResources().getConfiguration().updateFrom(backup);
assertEquals(backup, service.getResources().getConfiguration());
}
}
// Verify.
assertEquals(newSpecification, service.currentKeyboardSpecification);
verifyAll();
}
@SmallTest
// UiThreadTest annotation to handle callback of shared preference.
@UiThreadTest
public void testPreferenceInitialization() {
MozcService service = createService();
SharedPreferences sharedPreferences = getSharedPreferences();
sharedPreferences.edit()
.putString("pref_portrait_keyboard_layout_key", KeyboardLayout.QWERTY.name())
.commit();
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
sessionExecutor.reset(isA(SessionHandlerFactory.class), same(service));
sessionExecutor.setLogging(anyBoolean());
sessionExecutor.setImposedConfig(isA(Config.class));
sessionExecutor.updateRequest(isA(Request.class), eq(Collections.<TouchEvent>emptyList()));
expectLastCall().asStub();
sessionExecutor.switchInputMode(same(Optional.<KeyEventInterface>absent()),
isA(CompositionMode.class),
anyObject(EvaluationCallback.class));
expectLastCall().asStub();
sessionExecutor.setConfig(isA(Config.class));
sessionExecutor.preferenceUsageStatsEvent(
sharedPreferences, getInstrumentation().getTargetContext().getResources());
replayAll();
invokeOnCreateInternal(service, null, sharedPreferences, getDefaultDeviceConfiguration(),
sessionExecutor);
verifyAll();
// Make sure the UI is initialized expectedly.
assertEquals(KeyboardLayout.QWERTY,
service.viewManager.getActiveSoftwareKeyboardModel().getKeyboardLayout());
}
@SmallTest
public void testSymbolHistoryStorageImpl_getAllHistory() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SymbolHistoryStorage storage = new SymbolHistoryStorageImpl(sessionExecutor);
class TestData extends Parameter {
final SymbolMajorCategory majorCategory;
final StorageType expectedStorageType;
final String expectedValue;
TestData(SymbolMajorCategory majorCategory, StorageType expectedStorageType,
String expectedValue) {
this.majorCategory = majorCategory;
this.expectedStorageType = expectedStorageType;
this.expectedValue = expectedValue;
}
}
TestData[] testDataList = {
new TestData(SymbolMajorCategory.SYMBOL, StorageType.SYMBOL_HISTORY, "SYMBOL_HISTORY"),
new TestData(SymbolMajorCategory.EMOTICON, StorageType.EMOTICON_HISTORY,
"EMOTICON_HISTORY"),
new TestData(SymbolMajorCategory.EMOJI, StorageType.EMOJI_HISTORY, "EMOJI_HISTORY"),
};
for (TestData testData : testDataList) {
resetAll();
expect(sessionExecutor.readAllFromStorage(testData.expectedStorageType))
.andReturn(Collections.singletonList(ByteString.copyFromUtf8(testData.expectedValue)));
replayAll();
assertEquals(testData.toString(),
Collections.singletonList(testData.expectedValue),
storage.getAllHistory(testData.majorCategory));
verifyAll();
}
}
@SmallTest
public void testSymbolHistoryStorageImpl_addHistory() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SymbolHistoryStorage storage = new SymbolHistoryStorageImpl(sessionExecutor);
class TestData extends Parameter {
final SymbolMajorCategory majorCategory;
final StorageType expectedStorageType;
final String expectedValue;
TestData(SymbolMajorCategory majorCategory, StorageType expectedStorageType,
String expectedValue) {
this.majorCategory = majorCategory;
this.expectedStorageType = expectedStorageType;
this.expectedValue = expectedValue;
}
}
TestData[] testDataList = {
new TestData(SymbolMajorCategory.SYMBOL, StorageType.SYMBOL_HISTORY, "SYMBOL_HISTORY"),
new TestData(SymbolMajorCategory.EMOTICON, StorageType.EMOTICON_HISTORY,
"EMOTICON_HISTORY"),
new TestData(SymbolMajorCategory.EMOJI, StorageType.EMOJI_HISTORY, "EMOJI_HISTORY"),
};
for (TestData testData : testDataList) {
resetAll();
sessionExecutor.insertToStorage(
testData.expectedStorageType,
testData.expectedValue,
Collections.singletonList(ByteString.copyFromUtf8(testData.expectedValue)));
replayAll();
storage.addHistory(testData.majorCategory, testData.expectedValue);
verifyAll();
}
}
@SmallTest
public void testMozcEventListener_onConversionCandidateSelected() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
// Set feedback listener and force to enable feedbacks.
resetAll();
FeedbackListener feedbackListener = createMock(FeedbackListener.class);
FeedbackManager feedbackManager = new FeedbackManager(feedbackListener);
feedbackManager.setHapticFeedbackEnabled(true);
feedbackManager.setHapticFeedbackDuration(100);
feedbackManager.setSoundFeedbackEnabled(true);
feedbackManager.setSoundFeedbackVolume(0.5f);
service.feedbackManager = feedbackManager;
// Expectation.
feedbackListener.onSound(AudioManager.FX_KEY_CLICK, 0.5f);
feedbackListener.onVibrate(100L);
sessionExecutor.submitCandidate(eq(0), eq(Optional.<Integer>absent()),
isA(EvaluationCallback.class));
replayAll();
// Invoke onConversionCandidateSelected.
service.viewManager.getEventListener()
.onConversionCandidateSelected(0, Optional.<Integer>absent());
verifyAll();
}
@SmallTest
public void testMozcEventListener_onSymbolCandidateSelected() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
InputConnection inputConnection = createStrictMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
// Set feedback listener and force to enable feedbacks.
resetAll();
FeedbackListener feedbackListener = createMock(FeedbackListener.class);
FeedbackManager feedbackManager = new FeedbackManager(feedbackListener);
feedbackManager.setHapticFeedbackEnabled(true);
feedbackManager.setHapticFeedbackDuration(100);
feedbackManager.setSoundFeedbackEnabled(true);
feedbackManager.setSoundFeedbackVolume(0.5f);
service.feedbackManager = feedbackManager;
// Expectation.
expect(inputConnection.beginBatchEdit()).andReturn(true);
expect(inputConnection.commitText("(^_^)", MozcUtil.CURSOR_POSITION_TAIL))
.andReturn(true);
expect(inputConnection.endBatchEdit()).andReturn(true);
feedbackListener.onSound(AudioManager.FX_KEY_CLICK, 0.5f);
feedbackListener.onVibrate(100);
SymbolHistoryStorage historyStorage = createMock(SymbolHistoryStorage.class);
historyStorage.addHistory(SymbolMajorCategory.EMOTICON, "(^_^)");
service.symbolHistoryStorage = historyStorage;
replayAll();
// Invoke onConversionCandidateSelected.
service.viewManager.getEventListener().onSymbolCandidateSelected(
SymbolMajorCategory.EMOTICON, "(^_^)", true);
verifyAll();
}
@SuppressWarnings("unchecked")
@SmallTest
public void testMozcEventListener_onNarrowModeChanged() {
SessionExecutor sessionExecutorMock = createMock(SessionExecutor.class);
MozcService service = initializeMozcService(new MozcService(), sessionExecutorMock);
ViewManagerInterface viewManagerMock = createMock(ViewManagerInterface.class);
service.viewManager = viewManagerMock;
ViewEventListener viewEventListener = service.new MozcEventListener();
Capture<Config> configCapture = new Capture<Config>();
// On transition from narrow mode to non-narrow mode, submit() should be called.
resetAll();
expect(viewManagerMock.isNarrowMode()).andStubReturn(false);
expect(viewManagerMock.isFloatingCandidateMode()).andStubReturn(false);
sessionExecutorMock.submit(same(service.renderResultCallback));
sessionExecutorMock.setImposedConfig(capture(configCapture));
replayAll();
viewEventListener.onNarrowModeChanged(false);
verifyAll();
assertEquals(SelectionShortcut.NO_SHORTCUT,
configCapture.getValue().getSelectionShortcut());
// On the opposite transition, submit() should not be called.
resetAll();
expect(viewManagerMock.isNarrowMode()).andStubReturn(true);
expect(viewManagerMock.isFloatingCandidateMode()).andStubReturn(true);
sessionExecutorMock.setImposedConfig(capture(configCapture));
replayAll();
viewEventListener.onNarrowModeChanged(true);
verifyAll();
assertEquals(SelectionShortcut.SHORTCUT_123456789,
configCapture.getValue().getSelectionShortcut());
// Don't use shortcut keys if floating candidate window is disabled.
resetAll();
expect(viewManagerMock.isNarrowMode()).andStubReturn(true);
expect(viewManagerMock.isFloatingCandidateMode()).andStubReturn(false);
sessionExecutorMock.setImposedConfig(capture(configCapture));
replayAll();
viewEventListener.onNarrowModeChanged(true);
verifyAll();
assertEquals(SelectionShortcut.NO_SHORTCUT,
configCapture.getValue().getSelectionShortcut());
}
@SmallTest
public void testOnUndo() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
resetAll();
List<TouchEvent> touchEventList = Collections.singletonList(TouchEvent.getDefaultInstance());
sessionExecutor.undoOrRewind(eq(touchEventList), isA(EvaluationCallback.class));
replayAll();
service.viewManager.getEventListener().onUndo(touchEventList);
verifyAll();
}
@SmallTest
public void testOnSubmitPreedit() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
resetAll();
sessionExecutor.submit(isA(EvaluationCallback.class));
replayAll();
// Call the method.
service.viewManager.getEventListener().onSubmitPreedit();
verifyAll();
}
@SmallTest
public void testOnExpandSuggestion() {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
MozcService service = createInitializedService(sessionExecutor);
resetAll();
sessionExecutor.expandSuggestion(isA(EvaluationCallback.class));
replayAll();
service.viewManager.getEventListener().onExpandSuggestion();
verifyAll();
}
@SmallTest
public void testPropagateClientSidePreference_touchUI() {
Resources resoureces = getInstrumentation().getTargetContext().getResources();
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
replayAll();
MozcService service = createInitializedService(sessionExecutor);
ViewManagerInterface viewManager = service.viewManager;
// Initialization (all fields are propagated).
{
ClientSidePreference clientSidePreference =
new ClientSidePreference(
true, 100, true, 100, false, KeyboardLayout.QWERTY, InputStyle.TOGGLE, false, false,
0, EmojiProviderType.DOCOMO, HardwareKeyMap.JAPANESE109A, SkinType.ORANGE_LIGHTGRAY,
true, LayoutAdjustment.FILL, 100);
service.propagateClientSidePreference(clientSidePreference);
// Check all the fields are propagated.
FeedbackManager feedbackManager = service.feedbackManager;
assertTrue(feedbackManager.isHapticFeedbackEnabled());
assertEquals(feedbackManager.getHapticFeedbackDuration(), 100L);
assertTrue(feedbackManager.isSoundFeedbackEnabled());
assertEquals(feedbackManager.getSoundFeedbackVolume(), 0.8f);
assertFalse(viewManager.isPopupEnabled());
JapaneseSoftwareKeyboardModel model = viewManager.getActiveSoftwareKeyboardModel();
assertEquals(KeyboardLayout.QWERTY, model.getKeyboardLayout());
assertEquals(InputStyle.TOGGLE, model.getInputStyle());
assertFalse(model.isQwertyLayoutForAlphabet());
assertFalse(viewManager.isFullscreenMode());
assertFalse(service.onEvaluateFullscreenMode());
assertEquals(EmojiProviderType.DOCOMO, viewManager.getEmojiProviderType());
assertEquals(HardwareKeyMap.JAPANESE109A, service.viewManager.getHardwareKeyMap());
assertEquals(LayoutAdjustment.FILL, viewManager.getLayoutAdjustment());
assertEquals(100, viewManager.getKeyboardHeightRatio());
}
// Test for partial update of each attribute.
class TestData extends Parameter {
final ClientSidePreference clientSidePreference;
final boolean expectHapticFeedback;
final long expectHapticFeedbackDuration;
final boolean expectSoundFeedback;
final float expectSoundFeedbackVolume;
final boolean expectPopupFeedback;
final KeyboardLayout expectKeyboardLayout;
final InputStyle expectInputStyle;
final boolean expectQwertyLayoutForAlphabet;
final boolean expectFullscreenMode;
final int expectFlickSensitivity;
final EmojiProviderType expectEmojiProviderType;
final HardwareKeyMap expectHardwareKeyMap;
final Skin expectSkin;
final boolean expectMicrophoneButtonEnabledByPreference;
final LayoutAdjustment expectLayoutAdjustment;
final int expectKeyboardHeight;
TestData(ClientSidePreference clientSidePreference,
boolean expectHapticFeedback, long expectHapticFeedbackDuration,
boolean expectSoundFeedback, float expectSoundFeedbackVolume,
boolean expectPopupFeedback,
KeyboardLayout expectKeyboardLayout,
InputStyle expectInputStyle,
boolean expectQwertyLayoutForAlphabet,
boolean expectFullscreenMode,
int expectFlickSensitivity,
EmojiProviderType expectEmojiProviderType,
HardwareKeyMap expectHardwareKeyMap,
Skin expectSkin,
boolean expectMicrophoneButtonEnabledByPreference,
LayoutAdjustment expectLayoutAdjustment,
int expectKeyboardHeight) {
this.clientSidePreference = clientSidePreference;
this.expectHapticFeedback = expectHapticFeedback;
this.expectHapticFeedbackDuration = expectHapticFeedbackDuration;
this.expectSoundFeedback = expectSoundFeedback;
this.expectSoundFeedbackVolume = expectSoundFeedbackVolume;
this.expectPopupFeedback = expectPopupFeedback;
this.expectKeyboardLayout = expectKeyboardLayout;
this.expectInputStyle = expectInputStyle;
this.expectQwertyLayoutForAlphabet = expectQwertyLayoutForAlphabet;
this.expectFullscreenMode = expectFullscreenMode;
this.expectFlickSensitivity = expectFlickSensitivity;
this.expectEmojiProviderType = expectEmojiProviderType;
this.expectHardwareKeyMap = expectHardwareKeyMap;
this.expectSkin = expectSkin;
this.expectMicrophoneButtonEnabledByPreference = expectMicrophoneButtonEnabledByPreference;
this.expectLayoutAdjustment = expectLayoutAdjustment;
this.expectKeyboardHeight = expectKeyboardHeight;
}
}
// Note: following test case should tested in this order, as former test cases should affect to
// latter ones.
TestData[] testDataList = {
new TestData(
new ClientSidePreference(
false, 10, true, 10, true, KeyboardLayout.QWERTY, InputStyle.TOGGLE, false, false,
0, EmojiProviderType.DOCOMO, HardwareKeyMap.DEFAULT, SkinType.ORANGE_LIGHTGRAY,
true, LayoutAdjustment.FILL, 100),
false,
10,
true,
0.08f,
true,
KeyboardLayout.QWERTY,
InputStyle.TOGGLE,
false,
false,
0,
EmojiProviderType.DOCOMO,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
true,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 20, true, 20, false, KeyboardLayout.QWERTY, InputStyle.TOGGLE, false, false,
-1, EmojiProviderType.DOCOMO, HardwareKeyMap.DEFAULT, SkinType.ORANGE_LIGHTGRAY,
false, LayoutAdjustment.FILL, 100),
false,
20,
true,
0.16f,
false,
KeyboardLayout.QWERTY,
InputStyle.TOGGLE,
false,
false,
-1,
EmojiProviderType.DOCOMO,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
false,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 30, false, 30, true, KeyboardLayout.QWERTY, InputStyle.TOGGLE, false, false,
2, EmojiProviderType.KDDI, HardwareKeyMap.DEFAULT, SkinType.ORANGE_LIGHTGRAY,
true, LayoutAdjustment.FILL, 100),
false,
30,
false,
0.24f,
true,
KeyboardLayout.QWERTY,
InputStyle.TOGGLE,
false,
false,
2,
EmojiProviderType.KDDI,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
true,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 40, false, 40, true, KeyboardLayout.TWELVE_KEYS, InputStyle.TOGGLE,
false, false, -3, EmojiProviderType.SOFTBANK, HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY, true, LayoutAdjustment.FILL, 100),
false,
40,
false,
0.32f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.TOGGLE,
false,
false,
-3,
EmojiProviderType.SOFTBANK,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
true,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 50, false, 50, true, KeyboardLayout.TWELVE_KEYS, InputStyle.FLICK,
false, false, 4, EmojiProviderType.DOCOMO, HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY, false, LayoutAdjustment.FILL, 100),
false,
50,
false,
0.4f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.FLICK,
false,
false,
4,
EmojiProviderType.DOCOMO,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
false,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 60, false, 60, true, KeyboardLayout.TWELVE_KEYS, InputStyle.FLICK,
true, false, -5, EmojiProviderType.KDDI, HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY, true, LayoutAdjustment.FILL, 100),
false,
60,
false,
0.48f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.FLICK,
true,
false,
-5,
EmojiProviderType.KDDI,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
true,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 70, false, 70, true, KeyboardLayout.TWELVE_KEYS, InputStyle.FLICK,
true, true, 6, EmojiProviderType.SOFTBANK, HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY, false, LayoutAdjustment.FILL, 100),
false,
70,
false,
0.56f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.FLICK,
true,
true,
6,
EmojiProviderType.SOFTBANK,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
false,
LayoutAdjustment.FILL, 100),
new TestData(
new ClientSidePreference(
false, 80, false, 80, true, KeyboardLayout.TWELVE_KEYS, InputStyle.FLICK,
true, true, -7, EmojiProviderType.SOFTBANK, HardwareKeyMap.JAPANESE109A,
SkinType.ORANGE_LIGHTGRAY, false, LayoutAdjustment.FILL, 100),
false,
80,
false,
0.64f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.FLICK,
true,
true,
-7,
EmojiProviderType.SOFTBANK,
HardwareKeyMap.JAPANESE109A,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
false,
LayoutAdjustment.FILL,
100),
new TestData(
new ClientSidePreference(
false, 90, false, 90, true, KeyboardLayout.TWELVE_KEYS, InputStyle.FLICK,
true, true, 8, EmojiProviderType.SOFTBANK, HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY, true, LayoutAdjustment.RIGHT, 100),
false,
90,
false,
0.72f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.FLICK,
true,
true,
8,
EmojiProviderType.SOFTBANK,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
true,
LayoutAdjustment.RIGHT,
100),
new TestData(
new ClientSidePreference(
false, 100, false, 100, true, KeyboardLayout.TWELVE_KEYS, InputStyle.FLICK,
true, true, 8, EmojiProviderType.SOFTBANK, HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY, true, LayoutAdjustment.RIGHT, 120),
false,
100,
false,
0.8f,
true,
KeyboardLayout.TWELVE_KEYS,
InputStyle.FLICK,
true,
true,
8,
EmojiProviderType.SOFTBANK,
HardwareKeyMap.DEFAULT,
SkinType.ORANGE_LIGHTGRAY.getSkin(resoureces),
true,
LayoutAdjustment.RIGHT,
120),
};
for (TestData testData : testDataList) {
service.propagateClientSidePreference(testData.clientSidePreference);
FeedbackManager feedbackManager = service.feedbackManager;
// Make sure all configurations are actually applied.
assertEquals(testData.toString(),
testData.expectHapticFeedback, feedbackManager.isHapticFeedbackEnabled());
assertEquals(testData.toString(),
testData.expectHapticFeedbackDuration,
feedbackManager.getHapticFeedbackDuration());
assertEquals(testData.toString(),
testData.expectSoundFeedback, feedbackManager.isSoundFeedbackEnabled());
// Check with an epsilon.
assertEquals(testData.toString(),
testData.expectSoundFeedbackVolume, feedbackManager.getSoundFeedbackVolume(),
0.0001f);
assertEquals(testData.toString(),
testData.expectPopupFeedback,
viewManager.isPopupEnabled());
JapaneseSoftwareKeyboardModel model = viewManager.getActiveSoftwareKeyboardModel();
assertEquals(testData.toString(), testData.expectKeyboardLayout, model.getKeyboardLayout());
assertEquals(testData.toString(), testData.expectInputStyle, model.getInputStyle());
assertEquals(testData.toString(),
testData.expectQwertyLayoutForAlphabet, model.isQwertyLayoutForAlphabet());
assertEquals(testData.toString(),
testData.expectFullscreenMode,
viewManager.isFullscreenMode());
assertEquals(testData.toString(),
testData.expectFullscreenMode, service.onEvaluateFullscreenMode());
assertEquals(testData.toString(),
testData.expectFlickSensitivity,
viewManager.getFlickSensitivity());
assertEquals(testData.toString(),
testData.expectEmojiProviderType,
viewManager.getEmojiProviderType());
assertEquals(testData.toString(),
testData.expectHardwareKeyMap, service.viewManager.getHardwareKeyMap());
assertEquals(testData.toString(),
testData.expectSkin,
viewManager.getSkin());
assertEquals(testData.toString(),
testData.expectMicrophoneButtonEnabledByPreference,
viewManager.isMicrophoneButtonEnabledByPreference());
assertEquals(testData.toString(),
testData.expectLayoutAdjustment,
viewManager.getLayoutAdjustment());
assertEquals(testData.toString(),
testData.expectKeyboardHeight,
viewManager.getKeyboardHeightRatio());
}
}
/**
* We cannot check whether a sound is played or not.
* Just a smoke test.
*/
@SmallTest
public void testOnSound() {
SessionExecutor sessionExecutor = createNiceMock(SessionExecutor.class);
replayAll();
MozcService service = createInitializedService(sessionExecutor);
FeedbackListener listener = service.feedbackManager.feedbackListener;
listener.onSound(AudioManager.FX_KEYPRESS_STANDARD, 0.1f);
listener.onSound(FeedbackEvent.NO_SOUND, 0.1f);
}
@SmallTest
public void testOnConfigurationChanged() throws SecurityException, IllegalArgumentException {
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
ViewManager viewManager = createMockBuilder(ViewManager.class)
.withConstructor(Context.class, ViewEventListener.class,
SymbolHistoryStorage.class, ImeSwitcher.class, MenuDialogListener.class)
.withArgs(getInstrumentation().getTargetContext(),
createNiceMock(ViewEventListener.class),
createNiceMock(SymbolHistoryStorage.class),
createNiceMock(ImeSwitcher.class),
createNiceMock(MenuDialogListener.class))
.addMockedMethods("onConfigurationChanged")
.createMock();
MozcService service = createService();
invokeOnCreateInternal(
service, viewManager, getSharedPreferences(), getDefaultDeviceConfiguration(),
sessionExecutor);
service.selectionTracker = selectionTracker;
service.inputBound = true;
Configuration deviceConfig = new Configuration();
deviceConfig.keyboard = Configuration.KEYBOARD_NOKEYS;
deviceConfig.keyboardHidden = Configuration.KEYBOARDHIDDEN_YES;
deviceConfig.hardKeyboardHidden = Configuration.KEYBOARDHIDDEN_UNDEFINED;
InputConnection inputConnection = createMock(InputConnection.class);
// restartInput() can be used to install our InputConnection safely.
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
resetAll();
expect(inputConnection.finishComposingText()).andReturn(true);
expect(selectionTracker.getLastSelectionStart()).andReturn(0);
expect(selectionTracker.getLastSelectionEnd()).andReturn(0);
selectionTracker.onConfigurationChanged();
sessionExecutor.resetContext();
sessionExecutor.switchInputMode(same(Optional.<KeyEventInterface>absent()),
isA(CompositionMode.class),
anyObject(EvaluationCallback.class));
expectLastCall().asStub();
sessionExecutor.updateRequest(isA(Request.class), eq(Collections.<TouchEvent>emptyList()));
expectLastCall().asStub();
viewManager.onConfigurationChanged(deviceConfig);
replayAll();
// Test on no-hardware-keyboard configuration.
service.onConfigurationChangedInternal(deviceConfig);
verifyAll();
Handler configurationChangedHandler = service.configurationChangedHandler;
assertTrue(configurationChangedHandler.hasMessages(0));
// Clean up the pending message.
configurationChangedHandler.removeMessages(0);
}
@SmallTest
public void testOnUpdateSelection_exactMatch() {
// Prepare the service.
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
MozcService service = createInitializedService(sessionExecutor);
service.selectionTracker = selectionTracker;
resetAll();
// No events for SessionExecutor are expected.
expect(selectionTracker.onUpdateSelection(0, 0, 1, 1, 0, 1))
.andReturn(SelectionTracker.DO_NOTHING);
replayAll();
service.onUpdateSelection(0, 0, 1, 1, 0, 1);
verifyAll();
}
@SmallTest
public void testOnUpdateSelection_resetContext() {
// Prepare the service.
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("isInputViewShown")
.createMock();
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
initializeMozcService(service, sessionExecutor);
service.inputBound = true;
InputConnection inputConnection = createMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
service.selectionTracker = selectionTracker;
resetAll();
expect(selectionTracker.onUpdateSelection(0, 0, 1, 1, 0, 1))
.andReturn(SelectionTracker.RESET_CONTEXT);
// If the keyboard view is shown, reset the composing text here.
expect(service.isInputViewShown()).andReturn(true);
expect(inputConnection.finishComposingText()).andReturn(true);
// The session should be reset.
sessionExecutor.resetContext();
replayAll();
service.onUpdateSelectionInternal(0, 0, 1, 1, 0, 1);
verifyAll();
}
@SmallTest
public void testOnUpdateSelection_resetContextInivisibleKeyboard() {
// Prepare the service.
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("isInputViewShown")
.createMock();
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
initializeMozcService(service, sessionExecutor);
InputConnection inputConnection = createMock(InputConnection.class);
service.onCreateInputMethodInterface().restartInput(inputConnection, new EditorInfo());
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
service.selectionTracker = selectionTracker;
resetAll();
expect(selectionTracker.onUpdateSelection(0, 0, 1, 1, 0, 1))
.andReturn(SelectionTracker.RESET_CONTEXT);
// If the keyboard view isn't shown, do NOT reset the composing text.
expect(service.isInputViewShown()).andReturn(false);
// The session should be reset.
sessionExecutor.resetContext();
replayAll();
service.onUpdateSelectionInternal(0, 0, 1, 1, 0, 1);
verifyAll();
}
@SmallTest
public void testOnUpdateSelection_moveCursor() {
// Prepare the service.
SessionExecutor sessionExecutor = createMock(SessionExecutor.class);
SelectionTracker selectionTracker = createMock(SelectionTracker.class);
MozcService service = createInitializedService(sessionExecutor);
service.selectionTracker = selectionTracker;
resetAll();
expect(selectionTracker.onUpdateSelection(0, 0, 1, 1, 0, 1))
.andReturn(5);
// Send moveCursor event as selectionTracker says.
sessionExecutor.moveCursor(eq(5), isA(EvaluationCallback.class));
replayAll();
service.onUpdateSelection(0, 0, 1, 1, 0, 1);
verifyAll();
}
@SmallTest
public void testGetInputFieldType() {
class TestData extends Parameter {
final int inputType;
final InputFieldType expectedMode;
TestData (int inputType, InputFieldType expectedMode) {
this.inputType = inputType;
this.expectedMode = expectedMode;
}
}
TestData[] testDataList = {
new TestData((InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
InputFieldType.PASSWORD),
new TestData((InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI
| InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
InputFieldType.NORMAL),
new TestData((InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
InputFieldType.PASSWORD),
new TestData((InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE
| InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
InputFieldType.NORMAL),
new TestData((InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
InputFieldType.NUMBER),
new TestData(InputType.TYPE_CLASS_PHONE,
InputFieldType.TEL),
new TestData(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
InputFieldType.PASSWORD),
};
for (TestData testData : testDataList) {
EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = testData.inputType;
assertEquals(testData.expectedMode, MozcService.getInputFieldType(editorInfo));
}
}
@SmallTest
public void testOnHardwareKeyboardCompositionModeChange() {
MozcService service = initializeService(createMockBuilder(MozcService.class)
.addMockedMethod("isInputViewShown").createMock());
Context context = getInstrumentation().getTargetContext();
ViewManager viewManager = createMockBuilder(ViewManager.class)
.withConstructor(Context.class, ViewEventListener.class,
SymbolHistoryStorage.class, ImeSwitcher.class, MenuDialogListener.class)
.withArgs(context, service.new MozcEventListener(),
createNiceMock(SymbolHistoryStorage.class),
createNiceMock(ImeSwitcher.class),
createNiceMock(MenuDialogListener.class))
.addMockedMethod("switchHardwareKeyboardCompositionMode")
.createMock();
invokeOnCreateInternal(
service, viewManager, getSharedPreferences(), getDefaultDeviceConfiguration(),
createNiceMock(SessionExecutor.class));
MozcView mozcView = viewManager.createMozcView(context);
resetAll();
expect(service.isInputViewShown()).andStubReturn(true);
viewManager.switchHardwareKeyboardCompositionMode(CompositionSwitchMode.TOGGLE);
replayAll();
mozcView.findViewById(R.id.hardware_composition_button).performClick();
verifyAll();
}
@SmallTest
public void testMaybeSetNarrowMode() {
ViewManager viewManager = createViewManagerMock(
getInstrumentation().getTargetContext(),
createNiceMock(ViewEventListener.class));
MozcService service = createService();
resetAll();
SessionExecutor sessionExecutorMock = createNiceMock(SessionExecutor.class);
expect(viewManager.isNarrowMode()).andStubReturn(false);
expect(viewManager.isFloatingCandidateMode()).andStubReturn(false);
viewManager.onConfigurationChanged(anyObject(Configuration.class));
replayAll();
invokeOnCreateInternal(
service, viewManager, null, getDefaultDeviceConfiguration(), sessionExecutorMock);
verifyAll();
Configuration configuration = new Configuration();
resetAll();
expect(viewManager.isNarrowMode()).andReturn(false);
expect(viewManager.hideSubInputView()).andReturn(true);
viewManager.setNarrowMode(true);
replayAll();
configuration.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
service.maybeSetNarrowMode(configuration);
verifyAll();
resetAll();
expect(viewManager.isNarrowMode()).andReturn(true);
viewManager.setNarrowMode(false);
replayAll();
configuration.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
service.maybeSetNarrowMode(configuration);
verifyAll();
resetAll();
replayAll();
configuration.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
service.maybeSetNarrowMode(configuration);
verifyAll();
resetAll();
replayAll();
configuration.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_UNDEFINED;
service.maybeSetNarrowMode(configuration);
verifyAll();
}
@SmallTest
@ApiLevel(17)
@TargetApi(17)
public void testHandleGenericMotionEvent() {
MozcService service = createMockBuilder(MozcService.class)
.addMockedMethods("isInputViewShown")
.createMock();
ViewEventListener eventListener = createNiceMock(ViewEventListener.class);
ViewManager viewManager =
createViewManagerMock(getInstrumentation().getTargetContext(), eventListener);
service.viewManager = viewManager;
MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0);
try {
expect(service.isInputViewShown()).andReturn(true);
expect(service.viewManager.isGenericMotionToConsume(motionEvent)).andReturn(true);
expect(service.viewManager.consumeGenericMotion(motionEvent)).andReturn(true);
replayAll();
assertTrue(service.onGenericMotionEvent(motionEvent));
// Do not handle the event
resetAll();
expect(service.isInputViewShown()).andReturn(false);
replayAll();
assertFalse(service.onGenericMotionEvent(motionEvent));
} finally {
motionEvent.recycle();
}
}
}