Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.latin;
     18 
     19 import android.content.Context;
     20 import android.graphics.Rect;
     21 import android.util.AttributeSet;
     22 import android.view.MotionEvent;
     23 import android.view.View;
     24 import android.widget.FrameLayout;
     25 
     26 import com.android.inputmethod.accessibility.AccessibilityUtils;
     27 import com.android.inputmethod.keyboard.MainKeyboardView;
     28 import com.android.inputmethod.latin.suggestions.MoreSuggestionsView;
     29 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
     30 
     31 public final class InputView extends FrameLayout {
     32     private final Rect mInputViewRect = new Rect();
     33     private MainKeyboardView mMainKeyboardView;
     34     private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder;
     35     private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler;
     36     private MotionEventForwarder<?, ?> mActiveForwarder;
     37 
     38     public InputView(final Context context, final AttributeSet attrs) {
     39         super(context, attrs, 0);
     40     }
     41 
     42     @Override
     43     protected void onFinishInflate() {
     44         final SuggestionStripView suggestionStripView =
     45                 (SuggestionStripView)findViewById(R.id.suggestion_strip_view);
     46         mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view);
     47         mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder(
     48                 mMainKeyboardView, suggestionStripView);
     49         mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler(
     50                 mMainKeyboardView, suggestionStripView);
     51     }
     52 
     53     public void setKeyboardTopPadding(final int keyboardTopPadding) {
     54         mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding);
     55     }
     56 
     57     @Override
     58     protected boolean dispatchHoverEvent(final MotionEvent event) {
     59         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()
     60                 && mMainKeyboardView.isShowingMoreKeysPanel()) {
     61             // With accessibility mode on, discard hover events while a more keys keyboard is shown.
     62             // The {@link MoreKeysKeyboard} receives hover events directly from the platform.
     63             return true;
     64         }
     65         return super.dispatchHoverEvent(event);
     66     }
     67 
     68     @Override
     69     public boolean onInterceptTouchEvent(final MotionEvent me) {
     70         final Rect rect = mInputViewRect;
     71         getGlobalVisibleRect(rect);
     72         final int index = me.getActionIndex();
     73         final int x = (int)me.getX(index) + rect.left;
     74         final int y = (int)me.getY(index) + rect.top;
     75 
     76         // The touch events that hit the top padding of keyboard should be forwarded to
     77         // {@link SuggestionStripView}.
     78         if (mKeyboardTopPaddingForwarder.onInterceptTouchEvent(x, y, me)) {
     79             mActiveForwarder = mKeyboardTopPaddingForwarder;
     80             return true;
     81         }
     82 
     83         // To cancel {@link MoreSuggestionsView}, we should intercept a touch event to
     84         // {@link MainKeyboardView} and dismiss the {@link MoreSuggestionsView}.
     85         if (mMoreSuggestionsViewCanceler.onInterceptTouchEvent(x, y, me)) {
     86             mActiveForwarder = mMoreSuggestionsViewCanceler;
     87             return true;
     88         }
     89 
     90         mActiveForwarder = null;
     91         return false;
     92     }
     93 
     94     @Override
     95     public boolean onTouchEvent(final MotionEvent me) {
     96         if (mActiveForwarder == null) {
     97             return super.onTouchEvent(me);
     98         }
     99 
    100         final Rect rect = mInputViewRect;
    101         getGlobalVisibleRect(rect);
    102         final int index = me.getActionIndex();
    103         final int x = (int)me.getX(index) + rect.left;
    104         final int y = (int)me.getY(index) + rect.top;
    105         return mActiveForwarder.onTouchEvent(x, y, me);
    106     }
    107 
    108     /**
    109      * This class forwards series of {@link MotionEvent}s from <code>SenderView</code> to
    110      * <code>ReceiverView</code>.
    111      *
    112      * @param  a {@link View} that may send a {@link MotionEvent} to <ReceiverView>.
    113      * @param  a {@link View} that receives forwarded {@link MotionEvent} from
    114      *     <SenderView>.
    115      */
    116     private static abstract class
    117             MotionEventForwarder<SenderView extends View, ReceiverView extends View> {
    118         protected final SenderView mSenderView;
    119         protected final ReceiverView mReceiverView;
    120 
    121         protected final Rect mEventSendingRect = new Rect();
    122         protected final Rect mEventReceivingRect = new Rect();
    123 
    124         public MotionEventForwarder(final SenderView senderView, final ReceiverView receiverView) {
    125             mSenderView = senderView;
    126             mReceiverView = receiverView;
    127         }
    128 
    129         // Return true if a touch event of global coordinate x, y needs to be forwarded.
    130         protected abstract boolean needsToForward(final int x, final int y);
    131 
    132         // Translate global x-coordinate to <code>ReceiverView</code> local coordinate.
    133         protected int translateX(final int x) {
    134             return x - mEventReceivingRect.left;
    135         }
    136 
    137         // Translate global y-coordinate to <code>ReceiverView</code> local coordinate.
    138         protected int translateY(final int y) {
    139             return y - mEventReceivingRect.top;
    140         }
    141 
    142         // Callback when a {@link MotionEvent} is forwarded.
    143         protected void onForwardingEvent(final MotionEvent me) {}
    144 
    145         // Returns true if a {@link MotionEvent} is needed to be forwarded to
    146         // <code>ReceiverView</code>. Otherwise returns false.
    147         public boolean onInterceptTouchEvent(final int x, final int y, final MotionEvent me) {
    148             // Forwards a {link MotionEvent} only if both <code>SenderView</code> and
    149             // <code>ReceiverView</code> are visible.
    150             if (mSenderView.getVisibility() != View.VISIBLE ||
    151                     mReceiverView.getVisibility() != View.VISIBLE) {
    152                 return false;
    153             }
    154             mSenderView.getGlobalVisibleRect(mEventSendingRect);
    155             if (!mEventSendingRect.contains(x, y)) {
    156                 return false;
    157             }
    158 
    159             if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
    160                 // If the down event happens in the forwarding area, successive
    161                 // {@link MotionEvent}s should be forwarded to <code>ReceiverView</code>.
    162                 if (needsToForward(x, y)) {
    163                     return true;
    164                 }
    165             }
    166 
    167             return false;
    168         }
    169 
    170         // Returns true if a {@link MotionEvent} is forwarded to <code>ReceiverView</code>.
    171         // Otherwise returns false.
    172         public boolean onTouchEvent(final int x, final int y, final MotionEvent me) {
    173             mReceiverView.getGlobalVisibleRect(mEventReceivingRect);
    174             // Translate global coordinates to <code>ReceiverView</code> local coordinates.
    175             me.setLocation(translateX(x), translateY(y));
    176             mReceiverView.dispatchTouchEvent(me);
    177             onForwardingEvent(me);
    178             return true;
    179         }
    180     }
    181 
    182     /**
    183      * This class forwards {@link MotionEvent}s happened in the top padding of
    184      * {@link MainKeyboardView} to {@link SuggestionStripView}.
    185      */
    186     private static class KeyboardTopPaddingForwarder
    187             extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
    188         private int mKeyboardTopPadding;
    189 
    190         public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView,
    191                 final SuggestionStripView suggestionStripView) {
    192             super(mainKeyboardView, suggestionStripView);
    193         }
    194 
    195         public void setKeyboardTopPadding(final int keyboardTopPadding) {
    196             mKeyboardTopPadding = keyboardTopPadding;
    197         }
    198 
    199         private boolean isInKeyboardTopPadding(final int y) {
    200             return y < mEventSendingRect.top + mKeyboardTopPadding;
    201         }
    202 
    203         @Override
    204         protected boolean needsToForward(final int x, final int y) {
    205             // Forwarding an event only when {@link MainKeyboardView} is visible.
    206             // Because the visibility of {@link MainKeyboardView} is controlled by its parent
    207             // view in {@link KeyboardSwitcher#setMainKeyboardFrame()}, we should check the
    208             // visibility of the parent view.
    209             final View mainKeyboardFrame = (View)mSenderView.getParent();
    210             return mainKeyboardFrame.getVisibility() == View.VISIBLE && isInKeyboardTopPadding(y);
    211         }
    212 
    213         @Override
    214         protected int translateY(final int y) {
    215             final int translatedY = super.translateY(y);
    216             if (isInKeyboardTopPadding(y)) {
    217                 // The forwarded event should have coordinates that are inside of the target.
    218                 return Math.min(translatedY, mEventReceivingRect.height() - 1);
    219             }
    220             return translatedY;
    221         }
    222     }
    223 
    224     /**
    225      * This class forwards {@link MotionEvent}s happened in the {@link MainKeyboardView} to
    226      * {@link SuggestionStripView} when the {@link MoreSuggestionsView} is showing.
    227      * {@link SuggestionStripView} dismisses {@link MoreSuggestionsView} when it receives any event
    228      * outside of it.
    229      */
    230     private static class MoreSuggestionsViewCanceler
    231             extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
    232         public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView,
    233                 final SuggestionStripView suggestionStripView) {
    234             super(mainKeyboardView, suggestionStripView);
    235         }
    236 
    237         @Override
    238         protected boolean needsToForward(final int x, final int y) {
    239             return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y);
    240         }
    241 
    242         @Override
    243         protected void onForwardingEvent(final MotionEvent me) {
    244             if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
    245                 mReceiverView.dismissMoreSuggestionsPanel();
    246             }
    247         }
    248     }
    249 }
    250