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