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 
InputView(final Context context, final AttributeSet attrs)38     public InputView(final Context context, final AttributeSet attrs) {
39         super(context, attrs, 0);
40     }
41 
42     @Override
onFinishInflate()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 
setKeyboardTopPadding(final int keyboardTopPadding)53     public void setKeyboardTopPadding(final int keyboardTopPadding) {
54         mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding);
55     }
56 
57     @Override
dispatchHoverEvent(final MotionEvent event)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
onInterceptTouchEvent(final MotionEvent me)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
onTouchEvent(final MotionEvent me)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 <SenderView> a {@link View} that may send a {@link MotionEvent} to <ReceiverView>.
113      * @param <ReceiverView> 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 
MotionEventForwarder(final SenderView senderView, final ReceiverView receiverView)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.
needsToForward(final int x, final int y)130         protected abstract boolean needsToForward(final int x, final int y);
131 
132         // Translate global x-coordinate to <code>ReceiverView</code> local coordinate.
translateX(final int x)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.
translateY(final int y)138         protected int translateY(final int y) {
139             return y - mEventReceivingRect.top;
140         }
141 
142         // Callback when a {@link MotionEvent} is forwarded.
onForwardingEvent(final MotionEvent me)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.
onInterceptTouchEvent(final int x, final int y, final MotionEvent me)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.
onTouchEvent(final int x, final int y, final MotionEvent me)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 
KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView, final SuggestionStripView suggestionStripView)190         public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView,
191                 final SuggestionStripView suggestionStripView) {
192             super(mainKeyboardView, suggestionStripView);
193         }
194 
setKeyboardTopPadding(final int keyboardTopPadding)195         public void setKeyboardTopPadding(final int keyboardTopPadding) {
196             mKeyboardTopPadding = keyboardTopPadding;
197         }
198 
isInKeyboardTopPadding(final int y)199         private boolean isInKeyboardTopPadding(final int y) {
200             return y < mEventSendingRect.top + mKeyboardTopPadding;
201         }
202 
203         @Override
needsToForward(final int x, final int y)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
translateY(final int y)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> {
MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView, final SuggestionStripView suggestionStripView)232         public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView,
233                 final SuggestionStripView suggestionStripView) {
234             super(mainKeyboardView, suggestionStripView);
235         }
236 
237         @Override
needsToForward(final int x, final int y)238         protected boolean needsToForward(final int x, final int y) {
239             return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y);
240         }
241 
242         @Override
onForwardingEvent(final MotionEvent me)243         protected void onForwardingEvent(final MotionEvent me) {
244             if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
245                 mReceiverView.dismissMoreSuggestionsPanel();
246             }
247         }
248     }
249 }
250