/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.latin;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.suggestions.MoreSuggestionsView;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
public final class InputView extends LinearLayout {
private final Rect mInputViewRect = new Rect();
private MainKeyboardView mMainKeyboardView;
private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder;
private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler;
private MotionEventForwarder, ?> mActiveForwarder;
public InputView(final Context context, final AttributeSet attrs) {
super(context, attrs, 0);
}
@Override
protected void onFinishInflate() {
final SuggestionStripView suggestionStripView =
(SuggestionStripView)findViewById(R.id.suggestion_strip_view);
mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view);
mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder(
mMainKeyboardView, suggestionStripView);
mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler(
mMainKeyboardView, suggestionStripView);
}
public void setKeyboardTopPadding(final int keyboardTopPadding) {
mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding);
}
@Override
protected boolean dispatchHoverEvent(final MotionEvent event) {
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()
&& mMainKeyboardView.isShowingMoreKeysPanel()) {
// With accessibility mode on, discard hover events while a more keys keyboard is shown.
// The {@link MoreKeysKeyboard} receives hover events directly from the platform.
return true;
}
return super.dispatchHoverEvent(event);
}
@Override
public boolean onInterceptTouchEvent(final MotionEvent me) {
final Rect rect = mInputViewRect;
getGlobalVisibleRect(rect);
final int index = me.getActionIndex();
final int x = (int)me.getX(index) + rect.left;
final int y = (int)me.getY(index) + rect.top;
// The touch events that hit the top padding of keyboard should be forwarded to
// {@link SuggestionStripView}.
if (mKeyboardTopPaddingForwarder.onInterceptTouchEvent(x, y, me)) {
mActiveForwarder = mKeyboardTopPaddingForwarder;
return true;
}
// To cancel {@link MoreSuggestionsView}, we should intercept a touch event to
// {@link MainKeyboardView} and dismiss the {@link MoreSuggestionsView}.
if (mMoreSuggestionsViewCanceler.onInterceptTouchEvent(x, y, me)) {
mActiveForwarder = mMoreSuggestionsViewCanceler;
return true;
}
mActiveForwarder = null;
return false;
}
@Override
public boolean onTouchEvent(final MotionEvent me) {
if (mActiveForwarder == null) {
return super.onTouchEvent(me);
}
final Rect rect = mInputViewRect;
getGlobalVisibleRect(rect);
final int index = me.getActionIndex();
final int x = (int)me.getX(index) + rect.left;
final int y = (int)me.getY(index) + rect.top;
return mActiveForwarder.onTouchEvent(x, y, me);
}
/**
* This class forwards series of {@link MotionEvent}s from SenderView
to
* ReceiverView
.
*
* @param a {@link View} that may send a {@link MotionEvent} to .
* @param a {@link View} that receives forwarded {@link MotionEvent} from
* .
*/
private static abstract class
MotionEventForwarder {
protected final SenderView mSenderView;
protected final ReceiverView mReceiverView;
protected final Rect mEventSendingRect = new Rect();
protected final Rect mEventReceivingRect = new Rect();
public MotionEventForwarder(final SenderView senderView, final ReceiverView receiverView) {
mSenderView = senderView;
mReceiverView = receiverView;
}
// Return true if a touch event of global coordinate x, y needs to be forwarded.
protected abstract boolean needsToForward(final int x, final int y);
// Translate global x-coordinate to ReceiverView
local coordinate.
protected int translateX(final int x) {
return x - mEventReceivingRect.left;
}
// Translate global y-coordinate to ReceiverView
local coordinate.
protected int translateY(final int y) {
return y - mEventReceivingRect.top;
}
// Callback when a {@link MotionEvent} is forwarded.
protected void onForwardingEvent(final MotionEvent me) {}
// Returns true if a {@link MotionEvent} is needed to be forwarded to
// ReceiverView
. Otherwise returns false.
public boolean onInterceptTouchEvent(final int x, final int y, final MotionEvent me) {
// Forwards a {link MotionEvent} only if both SenderView
and
// ReceiverView
are visible.
if (mSenderView.getVisibility() != View.VISIBLE ||
mReceiverView.getVisibility() != View.VISIBLE) {
return false;
}
mSenderView.getGlobalVisibleRect(mEventSendingRect);
if (!mEventSendingRect.contains(x, y)) {
return false;
}
if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
// If the down event happens in the forwarding area, successive
// {@link MotionEvent}s should be forwarded to ReceiverView
.
if (needsToForward(x, y)) {
return true;
}
}
return false;
}
// Returns true if a {@link MotionEvent} is forwarded to ReceiverView
.
// Otherwise returns false.
public boolean onTouchEvent(final int x, final int y, final MotionEvent me) {
mReceiverView.getGlobalVisibleRect(mEventReceivingRect);
// Translate global coordinates to ReceiverView
local coordinates.
me.setLocation(translateX(x), translateY(y));
mReceiverView.dispatchTouchEvent(me);
onForwardingEvent(me);
return true;
}
}
/**
* This class forwards {@link MotionEvent}s happened in the top padding of
* {@link MainKeyboardView} to {@link SuggestionStripView}.
*/
private static class KeyboardTopPaddingForwarder
extends MotionEventForwarder {
private int mKeyboardTopPadding;
public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView,
final SuggestionStripView suggestionStripView) {
super(mainKeyboardView, suggestionStripView);
}
public void setKeyboardTopPadding(final int keyboardTopPadding) {
mKeyboardTopPadding = keyboardTopPadding;
}
private boolean isInKeyboardTopPadding(final int y) {
return y < mEventSendingRect.top + mKeyboardTopPadding;
}
@Override
protected boolean needsToForward(final int x, final int y) {
// Forwarding an event only when {@link MainKeyboardView} is visible.
// Because the visibility of {@link MainKeyboardView} is controlled by its parent
// view in {@link KeyboardSwitcher#setMainKeyboardFrame()}, we should check the
// visibility of the parent view.
final View mainKeyboardFrame = (View)mSenderView.getParent();
return mainKeyboardFrame.getVisibility() == View.VISIBLE && isInKeyboardTopPadding(y);
}
@Override
protected int translateY(final int y) {
final int translatedY = super.translateY(y);
if (isInKeyboardTopPadding(y)) {
// The forwarded event should have coordinates that are inside of the target.
return Math.min(translatedY, mEventReceivingRect.height() - 1);
}
return translatedY;
}
}
/**
* This class forwards {@link MotionEvent}s happened in the {@link MainKeyboardView} to
* {@link SuggestionStripView} when the {@link MoreSuggestionsView} is showing.
* {@link SuggestionStripView} dismisses {@link MoreSuggestionsView} when it receives any event
* outside of it.
*/
private static class MoreSuggestionsViewCanceler
extends MotionEventForwarder {
public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView,
final SuggestionStripView suggestionStripView) {
super(mainKeyboardView, suggestionStripView);
}
@Override
protected boolean needsToForward(final int x, final int y) {
return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y);
}
@Override
protected void onForwardingEvent(final MotionEvent me) {
if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
mReceiverView.dismissMoreSuggestionsPanel();
}
}
}
}