blob: ed68863eb663546286acc4d608930cf29a936922 [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 org.mozc.android.inputmethod.japanese.keyboard.BackgroundDrawableFactory.DrawableType;
import org.mozc.android.inputmethod.japanese.keyboard.Flick;
import org.mozc.android.inputmethod.japanese.keyboard.Flick.Direction;
import org.mozc.android.inputmethod.japanese.keyboard.Key;
import org.mozc.android.inputmethod.japanese.keyboard.Key.Stick;
import org.mozc.android.inputmethod.japanese.keyboard.KeyEntity;
import org.mozc.android.inputmethod.japanese.keyboard.KeyEventContext;
import org.mozc.android.inputmethod.japanese.keyboard.KeyEventHandler;
import org.mozc.android.inputmethod.japanese.keyboard.KeyState;
import org.mozc.android.inputmethod.japanese.keyboard.PopUp;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchAction;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import java.util.Collections;
/**
* This class is an event listener for a view which sends a key to MozcServer.
*
* Note that currently, we assume all key are repeatable based on our
* requirements. We can easily support non repeatable key if necessary.
*
*/
public class KeyEventButtonTouchListener implements OnTouchListener {
private final int sourceId;
private final int keyCode;
private KeyEventHandler keyEventHandler = null;
@VisibleForTesting KeyEventContext keyEventContext = null;
/**
* This is exported as protected for testing.
*/
protected KeyEventButtonTouchListener(int sourceId, int keyCode) {
this.sourceId = sourceId;
this.keyCode = keyCode;
}
/**
* Resets the internal state of this listener.
* This is exported as protected for testing.
*/
protected void reset() {
KeyEventHandler keyEventHandler = this.keyEventHandler;
KeyEventContext keyEventContext = this.keyEventContext;
if (keyEventHandler != null && keyEventContext != null) {
keyEventHandler.cancelDelayedKeyEvent(keyEventContext);
}
this.keyEventContext = null;
}
/**
* Sets a new event handler to this instance.
* This is exported as protected for testing.
*/
protected void setKeyEventHandler(KeyEventHandler keyEventHandler) {
// We'll detach the current event context from the old key event handler.
reset();
this.keyEventHandler = keyEventHandler;
}
/**
* Creates a (pseudo) {@link Key} instance based on the given {@code button} and its
* {@code keyCode}.
* This is exported as package private for testing.
*/
@VisibleForTesting static Key createKey(View button, int sourceId, int keyCode) {
KeyEntity keyEntity = new KeyEntity(
sourceId, keyCode, KeyEntity.INVALID_KEY_CODE, true, 0,
Optional.<String>absent(), false,
Optional.<PopUp>absent(), 0, 0, 0, 0);
Flick flick = new Flick(Direction.CENTER, keyEntity);
KeyState keyState =
new KeyState("",
Collections.<KeyState.MetaState>emptySet(),
Collections.<KeyState.MetaState>emptySet(),
Collections.<KeyState.MetaState>emptySet(),
Collections.singletonList(flick));
// Now, we support repeatable keys only.
return new Key(0, 0, button.getWidth(), button.getHeight(), 0, 0,
true, false, Stick.EVEN, DrawableType.TWELVEKEYS_REGULAR_KEY_BACKGROUND,
Collections.singletonList(keyState));
}
private static KeyEventContext createKeyEventContext(
View button, int sourceId, int keyCode, float x, float y) {
Key key = createKey(button, sourceId, keyCode);
View parent = View.class.cast(button.getParent());
return new KeyEventContext(
key, 0, x, y, parent.getWidth(), parent.getHeight(), 0,
Collections.<KeyState.MetaState>emptySet());
}
/**
* Handles the button view's down event.
* This is exported as package private for testing purpose.
*/
void onDown(View button, float x, float y) {
button.setPressed(true);
KeyEventHandler keyEventHandler = this.keyEventHandler;
if (keyEventHandler != null) {
// At first, cancel pending repeat events and overwrite it by the given keyCode.
KeyEventContext oldKeyEventContext = this.keyEventContext;
if (oldKeyEventContext != null) {
keyEventHandler.cancelDelayedKeyEvent(keyEventContext);
}
// Then create and start running new event. Note that we don't need create it
// if keyEventHandler is not set, because it will be reset in setKeyEventHandler
// when a new keyEventHandler is set.
KeyEventContext newContext = createKeyEventContext(button, sourceId, keyCode, x, y);
newContext.update(x, y, TouchAction.TOUCH_DOWN, 0);
keyEventHandler.maybeStartDelayedKeyEvent(newContext);
keyEventHandler.sendPress(newContext.getPressedKeyCode());
this.keyEventContext = newContext;
}
}
/**
* Handles the button view's up event.
* This is exported as package private for testing purpose.
*/
void onUp(View button, float x, float y, long timestamp) {
button.setPressed(false);
KeyEventHandler keyEventHandler = this.keyEventHandler;
KeyEventContext keyEventContext = this.keyEventContext;
if (keyEventHandler != null && keyEventContext != null) {
keyEventContext.update(x, y, TouchAction.TOUCH_UP, timestamp);
keyEventHandler.cancelDelayedKeyEvent(keyEventContext);
// TODO(hsumita): Confirm that we can put null as a touch event or not.
keyEventHandler.sendKey(keyEventContext.getKeyCode(),
Collections.singletonList(keyEventContext.getTouchEvent().orNull()));
keyEventHandler.sendRelease(keyEventContext.getPressedKeyCode());
}
this.keyEventContext = null;
}
/**
* Handles the button view's move event.
* This is exported as package private for testing purpose.
*/
void onMove(float x, float y, long timestamp) {
KeyEventContext keyEventContext = this.keyEventContext;
if (keyEventContext != null &&
keyEventContext.update(x, y, TouchAction.TOUCH_MOVE, timestamp)) {
// Here, the user's touching point is moved to outside of the button view,
// so cancel the repeating key event.
KeyEventHandler keyEventHandler = this.keyEventHandler;
if (keyEventHandler != null) {
keyEventHandler.cancelDelayedKeyEvent(keyEventContext);
}
this.keyEventContext = null;
}
}
@Override
public boolean onTouch(View button, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
onDown(button, event.getX(), event.getY());
break;
case MotionEvent.ACTION_UP:
onUp(button, event.getX(), event.getY(), event.getEventTime() - event.getDownTime());
break;
case MotionEvent.ACTION_MOVE:
onMove(event.getX(), event.getY(), event.getEventTime() - event.getDownTime());
break;
default:
// Ignore other events intentionally.
}
// Consume all the events here in order not to propagate the event to views behind the button.
return true;
}
}