/* * 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.FrameLayout; 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 FrameLayout { 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(); } } } }