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         /**
    143          * Callback when a {@link MotionEvent} is forwarded.
    144          * @param me the motion event to be forwarded.
    145          */
    146         protected void onForwardingEvent(final MotionEvent me) {}
    147 
    148         // Returns true if a {@link MotionEvent} is needed to be forwarded to
    149         // <code>ReceiverView</code>. Otherwise returns false.
    150         public boolean onInterceptTouchEvent(final int x, final int y, final MotionEvent me) {
    151             // Forwards a {link MotionEvent} only if both <code>SenderView</code> and
    152             // <code>ReceiverView</code> are visible.
    153             if (mSenderView.getVisibility() != View.VISIBLE ||
    154                     mReceiverView.getVisibility() != View.VISIBLE) {
    155                 return false;
    156             }
    157             mSenderView.getGlobalVisibleRect(mEventSendingRect);
    158             if (!mEventSendingRect.contains(x, y)) {
    159                 return false;
    160             }
    161 
    162             if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
    163                 // If the down event happens in the forwarding area, successive
    164                 // {@link MotionEvent}s should be forwarded to <code>ReceiverView</code>.
    165                 if (needsToForward(x, y)) {
    166                     return true;
    167                 }
    168             }
    169 
    170             return false;
    171         }
    172 
    173         // Returns true if a {@link MotionEvent} is forwarded to <code>ReceiverView</code>.
    174         // Otherwise returns false.
    175         public boolean onTouchEvent(final int x, final int y, final MotionEvent me) {
    176             mReceiverView.getGlobalVisibleRect(mEventReceivingRect);
    177             // Translate global coordinates to <code>ReceiverView</code> local coordinates.
    178             me.setLocation(translateX(x), translateY(y));
    179             mReceiverView.dispatchTouchEvent(me);
    180             onForwardingEvent(me);
    181             return true;
    182         }
    183     }
    184 
    185     /**
    186      * This class forwards {@link MotionEvent}s happened in the top padding of
    187      * {@link MainKeyboardView} to {@link SuggestionStripView}.
    188      */
    189     private static class KeyboardTopPaddingForwarder
    190             extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
    191         private int mKeyboardTopPadding;
    192 
    193         public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView,
    194                 final SuggestionStripView suggestionStripView) {
    195             super(mainKeyboardView, suggestionStripView);
    196         }
    197 
    198         public void setKeyboardTopPadding(final int keyboardTopPadding) {
    199             mKeyboardTopPadding = keyboardTopPadding;
    200         }
    201 
    202         private boolean isInKeyboardTopPadding(final int y) {
    203             return y < mEventSendingRect.top + mKeyboardTopPadding;
    204         }
    205 
    206         @Override
    207         protected boolean needsToForward(final int x, final int y) {
    208             // Forwarding an event only when {@link MainKeyboardView} is visible.
    209             // Because the visibility of {@link MainKeyboardView} is controlled by its parent
    210             // view in {@link KeyboardSwitcher#setMainKeyboardFrame()}, we should check the
    211             // visibility of the parent view.
    212             final View mainKeyboardFrame = (View)mSenderView.getParent();
    213             return mainKeyboardFrame.getVisibility() == View.VISIBLE && isInKeyboardTopPadding(y);
    214         }
    215 
    216         @Override
    217         protected int translateY(final int y) {
    218             final int translatedY = super.translateY(y);
    219             if (isInKeyboardTopPadding(y)) {
    220                 // The forwarded event should have coordinates that are inside of the target.
    221                 return Math.min(translatedY, mEventReceivingRect.height() - 1);
    222             }
    223             return translatedY;
    224         }
    225     }
    226 
    227     /**
    228      * This class forwards {@link MotionEvent}s happened in the {@link MainKeyboardView} to
    229      * {@link SuggestionStripView} when the {@link MoreSuggestionsView} is showing.
    230      * {@link SuggestionStripView} dismisses {@link MoreSuggestionsView} when it receives any event
    231      * outside of it.
    232      */
    233     private static class MoreSuggestionsViewCanceler
    234             extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
    235         public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView,
    236                 final SuggestionStripView suggestionStripView) {
    237             super(mainKeyboardView, suggestionStripView);
    238         }
    239 
    240         @Override
    241         protected boolean needsToForward(final int x, final int y) {
    242             return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y);
    243         }
    244 
    245         @Override
    246         protected void onForwardingEvent(final MotionEvent me) {
    247             if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
    248                 mReceiverView.dismissMoreSuggestionsPanel();
    249             }
    250         }
    251     }
    252 }
    253