blob: 45f067b9529863c9f05cecf00dfe8686153c58dd [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008-2009 Google Inc.
John Reckd0374c62015-10-20 13:25:01 -07003 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
John Reckd0374c62015-10-20 13:25:01 -07007 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008 * http://www.apache.org/licenses/LICENSE-2.0
John Reckd0374c62015-10-20 13:25:01 -07009 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080010 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.inputmethodservice;
18
Mathew Inwood1dd7d112018-07-31 14:53:29 +010019import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.TypedArray;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070022import android.graphics.Bitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.graphics.Canvas;
24import android.graphics.Paint;
Tarandeep Singhd59c6952018-05-18 13:53:53 -070025import android.graphics.Paint.Align;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070026import android.graphics.PorterDuff;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.graphics.Rect;
Svetoslav Ganov0d67c892011-08-06 08:06:46 -070028import android.graphics.Typeface;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.graphics.drawable.Drawable;
30import android.inputmethodservice.Keyboard.Key;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -070031import android.media.AudioManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.os.Handler;
33import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.util.AttributeSet;
Amith Yamasanie4037002009-07-23 17:38:15 -070035import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.view.GestureDetector;
37import android.view.Gravity;
38import android.view.LayoutInflater;
39import android.view.MotionEvent;
40import android.view.View;
Amith Yamasanib974c7a2009-07-21 15:05:39 -070041import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.view.ViewGroup.LayoutParams;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -070043import android.view.accessibility.AccessibilityEvent;
44import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.widget.PopupWindow;
46import android.widget.TextView;
47
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070048import com.android.internal.R;
49
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import java.util.Arrays;
51import java.util.HashMap;
52import java.util.List;
53import java.util.Map;
54
55/**
56 * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
57 * detecting key presses and touch movements.
John Reckd0374c62015-10-20 13:25:01 -070058 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 * @attr ref android.R.styleable#KeyboardView_keyBackground
60 * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
61 * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
Jonathan Dormodybfaf8622018-01-22 11:19:55 -070062 * @attr ref android.R.styleable#KeyboardView_keyPreviewHeight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 * @attr ref android.R.styleable#KeyboardView_labelTextSize
64 * @attr ref android.R.styleable#KeyboardView_keyTextSize
65 * @attr ref android.R.styleable#KeyboardView_keyTextColor
66 * @attr ref android.R.styleable#KeyboardView_verticalCorrection
67 * @attr ref android.R.styleable#KeyboardView_popupLayout
Yohei Yukawab7a284e2018-12-13 15:53:16 -080068 *
69 * @deprecated This class is deprecated because this is just a convenient UI widget class that
70 * application developers can re-implement on top of existing public APIs. If you have
71 * already depended on this class, consider copying the implementation from AOSP into
72 * your project or re-implementing a similar widget by yourselves
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 */
Yohei Yukawab7a284e2018-12-13 15:53:16 -080074@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075public class KeyboardView extends View implements View.OnClickListener {
76
77 /**
78 * Listener for virtual keyboard events.
79 */
80 public interface OnKeyboardActionListener {
John Reckd0374c62015-10-20 13:25:01 -070081
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 /**
83 * Called when the user presses a key. This is sent before the {@link #onKey} is called.
84 * For keys that repeat, this is only called once.
85 * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
86 * key, the value will be zero.
87 */
88 void onPress(int primaryCode);
John Reckd0374c62015-10-20 13:25:01 -070089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 /**
91 * Called when the user releases a key. This is sent after the {@link #onKey} is called.
92 * For keys that repeat, this is only called once.
93 * @param primaryCode the code of the key that was released
94 */
95 void onRelease(int primaryCode);
96
97 /**
98 * Send a key press to the listener.
99 * @param primaryCode this is the key that was pressed
100 * @param keyCodes the codes for all the possible alternative keys
101 * with the primary code being the first. If the primary key code is
102 * a single character such as an alphabet or number or symbol, the alternatives
103 * will include other characters that may be on the same key or adjacent keys.
104 * These codes are useful to correct for accidental presses of a key adjacent to
105 * the intended key.
106 */
107 void onKey(int primaryCode, int[] keyCodes);
108
109 /**
110 * Sends a sequence of characters to the listener.
111 * @param text the sequence of characters to be displayed.
112 */
113 void onText(CharSequence text);
John Reckd0374c62015-10-20 13:25:01 -0700114
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 /**
116 * Called when the user quickly moves the finger from right to left.
117 */
118 void swipeLeft();
John Reckd0374c62015-10-20 13:25:01 -0700119
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 /**
121 * Called when the user quickly moves the finger from left to right.
122 */
123 void swipeRight();
John Reckd0374c62015-10-20 13:25:01 -0700124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 /**
126 * Called when the user quickly moves the finger from up to down.
127 */
128 void swipeDown();
John Reckd0374c62015-10-20 13:25:01 -0700129
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 /**
131 * Called when the user quickly moves the finger from down to up.
132 */
133 void swipeUp();
134 }
135
136 private static final boolean DEBUG = false;
137 private static final int NOT_A_KEY = -1;
138 private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
John Reckd0374c62015-10-20 13:25:01 -0700139 private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
140
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 private Keyboard mKeyboard;
142 private int mCurrentKeyIndex = NOT_A_KEY;
Mathew Inwood1dd7d112018-07-31 14:53:29 +0100143 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 private int mLabelTextSize;
145 private int mKeyTextSize;
146 private int mKeyTextColor;
147 private float mShadowRadius;
148 private int mShadowColor;
149 private float mBackgroundDimAmount;
John Reckd0374c62015-10-20 13:25:01 -0700150
Mathew Inwood1dd7d112018-07-31 14:53:29 +0100151 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 private TextView mPreviewText;
153 private PopupWindow mPreviewPopup;
154 private int mPreviewTextSizeLarge;
155 private int mPreviewOffset;
156 private int mPreviewHeight;
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900157 // Working variable
158 private final int[] mCoordinates = new int[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159
160 private PopupWindow mPopupKeyboard;
161 private View mMiniKeyboardContainer;
162 private KeyboardView mMiniKeyboard;
163 private boolean mMiniKeyboardOnScreen;
164 private View mPopupParent;
165 private int mMiniKeyboardOffsetX;
166 private int mMiniKeyboardOffsetY;
167 private Map<Key,View> mMiniKeyboardCache;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 private Key[] mKeys;
169
170 /** Listener for {@link OnKeyboardActionListener}. */
171 private OnKeyboardActionListener mKeyboardActionListener;
John Reckd0374c62015-10-20 13:25:01 -0700172
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 private static final int MSG_SHOW_PREVIEW = 1;
174 private static final int MSG_REMOVE_PREVIEW = 2;
175 private static final int MSG_REPEAT = 3;
176 private static final int MSG_LONGPRESS = 4;
Amith Yamasanie4037002009-07-23 17:38:15 -0700177
Amith Yamasani29d85df2009-08-11 16:42:51 -0700178 private static final int DELAY_BEFORE_PREVIEW = 0;
179 private static final int DELAY_AFTER_PREVIEW = 70;
Amith Yamasani7aa936e2010-02-11 18:51:25 -0800180 private static final int DEBOUNCE_TIME = 70;
John Reckd0374c62015-10-20 13:25:01 -0700181
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 private int mVerticalCorrection;
183 private int mProximityThreshold;
184
185 private boolean mPreviewCentered = false;
186 private boolean mShowPreview = true;
187 private boolean mShowTouchPoints = true;
188 private int mPopupPreviewX;
189 private int mPopupPreviewY;
190
191 private int mLastX;
192 private int mLastY;
193 private int mStartX;
194 private int mStartY;
195
196 private boolean mProximityCorrectOn;
John Reckd0374c62015-10-20 13:25:01 -0700197
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 private Paint mPaint;
199 private Rect mPadding;
John Reckd0374c62015-10-20 13:25:01 -0700200
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 private long mDownTime;
202 private long mLastMoveTime;
203 private int mLastKey;
204 private int mLastCodeX;
205 private int mLastCodeY;
206 private int mCurrentKey = NOT_A_KEY;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700207 private int mDownKey = NOT_A_KEY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 private long mLastKeyTime;
209 private long mCurrentKeyTime;
210 private int[] mKeyIndices = new int[12];
211 private GestureDetector mGestureDetector;
212 private int mPopupX;
213 private int mPopupY;
214 private int mRepeatKeyIndex = NOT_A_KEY;
215 private int mPopupLayout;
216 private boolean mAbortKey;
217 private Key mInvalidatedKey;
218 private Rect mClipRegion = new Rect(0, 0, 0, 0);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700219 private boolean mPossiblePoly;
220 private SwipeTracker mSwipeTracker = new SwipeTracker();
221 private int mSwipeThreshold;
222 private boolean mDisambiguateSwipe;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700223
224 // Variables for dealing with multiple pointers
225 private int mOldPointerCount = 1;
226 private float mOldPointerX;
227 private float mOldPointerY;
228
Mathew Inwood1dd7d112018-07-31 14:53:29 +0100229 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 private Drawable mKeyBackground;
231
232 private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
233 private static final int REPEAT_START_DELAY = 400;
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700234 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235
236 private static int MAX_NEARBY_KEYS = 12;
237 private int[] mDistances = new int[MAX_NEARBY_KEYS];
238
239 // For multi-tap
240 private int mLastSentIndex;
241 private int mTapCount;
242 private long mLastTapTime;
243 private boolean mInMultiTap;
244 private static final int MULTITAP_INTERVAL = 800; // milliseconds
245 private StringBuilder mPreviewLabel = new StringBuilder(1);
246
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700247 /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
248 private boolean mDrawPending;
249 /** The dirty region in the keyboard bitmap */
250 private Rect mDirtyRect = new Rect();
251 /** The keyboard bitmap for faster updates */
252 private Bitmap mBuffer;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700253 /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
254 private boolean mKeyboardChanged;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700255 /** The canvas for the above mutable keyboard bitmap */
256 private Canvas mCanvas;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700257 /** The accessibility manager for accessibility support */
258 private AccessibilityManager mAccessibilityManager;
259 /** The audio manager for accessibility support */
260 private AudioManager mAudioManager;
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700261 /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
262 private boolean mHeadsetRequiredToHearPasswordsAnnounced;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700263
John Reckd0374c62015-10-20 13:25:01 -0700264 Handler mHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265
266 public KeyboardView(Context context, AttributeSet attrs) {
267 this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
268 }
269
Alan Viverette617feb92013-09-09 18:09:13 -0700270 public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
271 this(context, attrs, defStyleAttr, 0);
272 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273
Alan Viverette617feb92013-09-09 18:09:13 -0700274 public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
275 super(context, attrs, defStyleAttr, defStyleRes);
276
277 TypedArray a = context.obtainStyledAttributes(
278 attrs, android.R.styleable.KeyboardView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279
280 LayoutInflater inflate =
281 (LayoutInflater) context
282 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
283
284 int previewLayout = 0;
285 int keyTextSize = 0;
286
287 int n = a.getIndexCount();
John Reckd0374c62015-10-20 13:25:01 -0700288
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 for (int i = 0; i < n; i++) {
290 int attr = a.getIndex(i);
291
292 switch (attr) {
293 case com.android.internal.R.styleable.KeyboardView_keyBackground:
294 mKeyBackground = a.getDrawable(attr);
295 break;
296 case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
297 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
298 break;
299 case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
300 previewLayout = a.getResourceId(attr, 0);
301 break;
302 case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
303 mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
304 break;
305 case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
306 mPreviewHeight = a.getDimensionPixelSize(attr, 80);
307 break;
308 case com.android.internal.R.styleable.KeyboardView_keyTextSize:
309 mKeyTextSize = a.getDimensionPixelSize(attr, 18);
310 break;
311 case com.android.internal.R.styleable.KeyboardView_keyTextColor:
312 mKeyTextColor = a.getColor(attr, 0xFF000000);
313 break;
314 case com.android.internal.R.styleable.KeyboardView_labelTextSize:
315 mLabelTextSize = a.getDimensionPixelSize(attr, 14);
316 break;
317 case com.android.internal.R.styleable.KeyboardView_popupLayout:
318 mPopupLayout = a.getResourceId(attr, 0);
319 break;
320 case com.android.internal.R.styleable.KeyboardView_shadowColor:
321 mShadowColor = a.getColor(attr, 0);
322 break;
323 case com.android.internal.R.styleable.KeyboardView_shadowRadius:
324 mShadowRadius = a.getFloat(attr, 0f);
325 break;
326 }
327 }
John Reckd0374c62015-10-20 13:25:01 -0700328
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 a = mContext.obtainStyledAttributes(
330 com.android.internal.R.styleable.Theme);
331 mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
332
333 mPreviewPopup = new PopupWindow(context);
334 if (previewLayout != 0) {
335 mPreviewText = (TextView) inflate.inflate(previewLayout, null);
336 mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
337 mPreviewPopup.setContentView(mPreviewText);
338 mPreviewPopup.setBackgroundDrawable(null);
339 } else {
340 mShowPreview = false;
341 }
John Reckd0374c62015-10-20 13:25:01 -0700342
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 mPreviewPopup.setTouchable(false);
John Reckd0374c62015-10-20 13:25:01 -0700344
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 mPopupKeyboard = new PopupWindow(context);
346 mPopupKeyboard.setBackgroundDrawable(null);
347 //mPopupKeyboard.setClippingEnabled(false);
John Reckd0374c62015-10-20 13:25:01 -0700348
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 mPopupParent = this;
350 //mPredicting = true;
John Reckd0374c62015-10-20 13:25:01 -0700351
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 mPaint = new Paint();
353 mPaint.setAntiAlias(true);
354 mPaint.setTextSize(keyTextSize);
355 mPaint.setTextAlign(Align.CENTER);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700356 mPaint.setAlpha(255);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357
358 mPadding = new Rect(0, 0, 0, 0);
359 mMiniKeyboardCache = new HashMap<Key,View>();
360 mKeyBackground.getPadding(mPadding);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700361
362 mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
363 mDisambiguateSwipe = getResources().getBoolean(
364 com.android.internal.R.bool.config_swipeDisambiguation);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700365
366 mAccessibilityManager = AccessibilityManager.getInstance(context);
367 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
368
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 resetMultiTap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
371
John Reckd0374c62015-10-20 13:25:01 -0700372 @Override
373 protected void onAttachedToWindow() {
374 super.onAttachedToWindow();
375 initGestureDetector();
376 if (mHandler == null) {
377 mHandler = new Handler() {
378 @Override
379 public void handleMessage(Message msg) {
380 switch (msg.what) {
381 case MSG_SHOW_PREVIEW:
382 showKey(msg.arg1);
383 break;
384 case MSG_REMOVE_PREVIEW:
385 mPreviewText.setVisibility(INVISIBLE);
386 break;
387 case MSG_REPEAT:
388 if (repeatKey()) {
389 Message repeat = Message.obtain(this, MSG_REPEAT);
390 sendMessageDelayed(repeat, REPEAT_INTERVAL);
391 }
392 break;
393 case MSG_LONGPRESS:
394 openPopupIfRequired((MotionEvent) msg.obj);
395 break;
396 }
397 }
398 };
399 }
400 }
Chet Haasea95e1082011-09-19 16:21:53 -0700401
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402 private void initGestureDetector() {
John Reckd0374c62015-10-20 13:25:01 -0700403 if (mGestureDetector == null) {
404 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
405 @Override
406 public boolean onFling(MotionEvent me1, MotionEvent me2,
407 float velocityX, float velocityY) {
408 if (mPossiblePoly) return false;
409 final float absX = Math.abs(velocityX);
410 final float absY = Math.abs(velocityY);
411 float deltaX = me2.getX() - me1.getX();
412 float deltaY = me2.getY() - me1.getY();
413 int travelX = getWidth() / 2; // Half the keyboard width
414 int travelY = getHeight() / 2; // Half the keyboard height
415 mSwipeTracker.computeCurrentVelocity(1000);
416 final float endingVelocityX = mSwipeTracker.getXVelocity();
417 final float endingVelocityY = mSwipeTracker.getYVelocity();
418 boolean sendDownKey = false;
419 if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
420 if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
421 sendDownKey = true;
422 } else {
423 swipeRight();
424 return true;
425 }
426 } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
427 if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
428 sendDownKey = true;
429 } else {
430 swipeLeft();
431 return true;
432 }
433 } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
434 if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
435 sendDownKey = true;
436 } else {
437 swipeUp();
438 return true;
439 }
440 } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
441 if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
442 sendDownKey = true;
443 } else {
444 swipeDown();
445 return true;
446 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700447 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700448
John Reckd0374c62015-10-20 13:25:01 -0700449 if (sendDownKey) {
450 detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
451 }
452 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 }
John Reckd0374c62015-10-20 13:25:01 -0700454 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455
John Reckd0374c62015-10-20 13:25:01 -0700456 mGestureDetector.setIsLongpressEnabled(false);
457 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 }
459
460 public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
461 mKeyboardActionListener = listener;
462 }
463
464 /**
465 * Returns the {@link OnKeyboardActionListener} object.
466 * @return the listener attached to this keyboard
467 */
468 protected OnKeyboardActionListener getOnKeyboardActionListener() {
469 return mKeyboardActionListener;
470 }
471
472 /**
473 * Attaches a keyboard to this view. The keyboard can be switched at any time and the
474 * view will re-layout itself to accommodate the keyboard.
475 * @see Keyboard
476 * @see #getKeyboard()
477 * @param keyboard the keyboard to display in this view
478 */
479 public void setKeyboard(Keyboard keyboard) {
480 if (mKeyboard != null) {
481 showPreview(NOT_A_KEY);
482 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700483 // Remove any pending messages
484 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 mKeyboard = keyboard;
486 List<Key> keys = mKeyboard.getKeys();
487 mKeys = keys.toArray(new Key[keys.size()]);
488 requestLayout();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700489 // Hint to reallocate the buffer if the size changed
490 mKeyboardChanged = true;
Amith Yamasanif04da952009-05-06 15:46:00 -0700491 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 computeProximityThreshold(keyboard);
493 mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700494 // Switching to a different keyboard should abort any pending keys so that the key up
495 // doesn't get delivered to the old or new keyboard
496 mAbortKey = true; // Until the next ACTION_DOWN
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 }
498
499 /**
500 * Returns the current keyboard being displayed by this view.
501 * @return the currently attached keyboard
502 * @see #setKeyboard(Keyboard)
503 */
504 public Keyboard getKeyboard() {
505 return mKeyboard;
506 }
John Reckd0374c62015-10-20 13:25:01 -0700507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 /**
509 * Sets the state of the shift key of the keyboard, if any.
510 * @param shifted whether or not to enable the state of the shift key
511 * @return true if the shift key state changed, false if there was no change
512 * @see KeyboardView#isShifted()
513 */
514 public boolean setShifted(boolean shifted) {
515 if (mKeyboard != null) {
516 if (mKeyboard.setShifted(shifted)) {
517 // The whole keyboard probably needs to be redrawn
Amith Yamasanif04da952009-05-06 15:46:00 -0700518 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 return true;
520 }
521 }
522 return false;
523 }
524
525 /**
526 * Returns the state of the shift key of the keyboard, if any.
527 * @return true if the shift is in a pressed state, false otherwise. If there is
528 * no shift key on the keyboard or there is no keyboard attached, it returns false.
529 * @see KeyboardView#setShifted(boolean)
530 */
531 public boolean isShifted() {
532 if (mKeyboard != null) {
533 return mKeyboard.isShifted();
534 }
535 return false;
536 }
537
538 /**
539 * Enables or disables the key feedback popup. This is a popup that shows a magnified
John Reckd0374c62015-10-20 13:25:01 -0700540 * version of the depressed key. By default the preview is enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 * @param previewEnabled whether or not to enable the key feedback popup
542 * @see #isPreviewEnabled()
543 */
544 public void setPreviewEnabled(boolean previewEnabled) {
545 mShowPreview = previewEnabled;
546 }
547
548 /**
549 * Returns the enabled state of the key feedback popup.
550 * @return whether or not the key feedback popup is enabled
551 * @see #setPreviewEnabled(boolean)
552 */
553 public boolean isPreviewEnabled() {
554 return mShowPreview;
555 }
John Reckd0374c62015-10-20 13:25:01 -0700556
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 public void setVerticalCorrection(int verticalOffset) {
John Reckd0374c62015-10-20 13:25:01 -0700558
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 }
560 public void setPopupParent(View v) {
561 mPopupParent = v;
562 }
John Reckd0374c62015-10-20 13:25:01 -0700563
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 public void setPopupOffset(int x, int y) {
565 mMiniKeyboardOffsetX = x;
566 mMiniKeyboardOffsetY = y;
567 if (mPreviewPopup.isShowing()) {
568 mPreviewPopup.dismiss();
569 }
570 }
571
572 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700573 * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
574 * codes for adjacent keys. When disabled, only the primary key code will be
575 * reported.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 * @param enabled whether or not the proximity correction is enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 */
578 public void setProximityCorrectionEnabled(boolean enabled) {
579 mProximityCorrectOn = enabled;
580 }
581
582 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700583 * Returns true if proximity correction is enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 */
585 public boolean isProximityCorrectionEnabled() {
586 return mProximityCorrectOn;
587 }
588
John Reckd0374c62015-10-20 13:25:01 -0700589 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 * Popup keyboard close button clicked.
John Reckd0374c62015-10-20 13:25:01 -0700591 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 */
593 public void onClick(View v) {
594 dismissPopupKeyboard();
595 }
596
597 private CharSequence adjustCase(CharSequence label) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800598 if (mKeyboard.isShifted() && label != null && label.length() < 3
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 && Character.isLowerCase(label.charAt(0))) {
600 label = label.toString().toUpperCase();
601 }
602 return label;
603 }
604
605 @Override
606 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
607 // Round up a little
608 if (mKeyboard == null) {
609 setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
610 } else {
611 int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
612 if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
613 width = MeasureSpec.getSize(widthMeasureSpec);
614 }
615 setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
616 }
617 }
618
619 /**
620 * Compute the average distance between adjacent keys (horizontally and vertically)
621 * and square it to get the proximity threshold. We use a square here and in computing
622 * the touch distance from a key's center to avoid taking a square root.
623 * @param keyboard
624 */
625 private void computeProximityThreshold(Keyboard keyboard) {
626 if (keyboard == null) return;
627 final Key[] keys = mKeys;
628 if (keys == null) return;
629 int length = keys.length;
630 int dimensionSum = 0;
631 for (int i = 0; i < length; i++) {
632 Key key = keys[i];
633 dimensionSum += Math.min(key.width, key.height) + key.gap;
634 }
635 if (dimensionSum < 0 || length == 0) return;
636 mProximityThreshold = (int) (dimensionSum * 1.4f / length);
637 mProximityThreshold *= mProximityThreshold; // Square it
638 }
639
640 @Override
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700641 public void onSizeChanged(int w, int h, int oldw, int oldh) {
642 super.onSizeChanged(w, h, oldw, oldh);
Chet Haasea95e1082011-09-19 16:21:53 -0700643 if (mKeyboard != null) {
644 mKeyboard.resize(w, h);
645 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700646 // Release the buffer, if any and it will be reallocated on the next draw
647 mBuffer = null;
648 }
649
650 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 public void onDraw(Canvas canvas) {
652 super.onDraw(canvas);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700653 if (mDrawPending || mBuffer == null || mKeyboardChanged) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700654 onBufferDraw();
655 }
656 canvas.drawBitmap(mBuffer, 0, 0, null);
657 }
Amith Yamasanie877ec62009-08-05 21:12:07 -0700658
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700659 private void onBufferDraw() {
Amith Yamasanie877ec62009-08-05 21:12:07 -0700660 if (mBuffer == null || mKeyboardChanged) {
661 if (mBuffer == null || mKeyboardChanged &&
662 (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
Romain Guy46a61bb2010-02-08 11:57:55 -0800663 // Make sure our bitmap is at least 1x1
664 final int width = Math.max(1, getWidth());
665 final int height = Math.max(1, getHeight());
666 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700667 mCanvas = new Canvas(mBuffer);
668 }
Amith Yamasanif04da952009-05-06 15:46:00 -0700669 invalidateAllKeys();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700670 mKeyboardChanged = false;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700671 }
John Reckd0374c62015-10-20 13:25:01 -0700672
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 if (mKeyboard == null) return;
John Reckd0374c62015-10-20 13:25:01 -0700674
Tarandeep Singhd59c6952018-05-18 13:53:53 -0700675 mCanvas.save();
676 final Canvas canvas = mCanvas;
677 canvas.clipRect(mDirtyRect);
678
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 final Paint paint = mPaint;
680 final Drawable keyBackground = mKeyBackground;
681 final Rect clipRegion = mClipRegion;
682 final Rect padding = mPadding;
683 final int kbdPaddingLeft = mPaddingLeft;
684 final int kbdPaddingTop = mPaddingTop;
685 final Key[] keys = mKeys;
686 final Key invalidKey = mInvalidatedKey;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700687
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 paint.setColor(mKeyTextColor);
689 boolean drawSingleKey = false;
690 if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700691 // Is clipRegion completely contained within the invalidated key?
692 if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
693 invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
694 invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
695 invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
696 drawSingleKey = true;
697 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700699 canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700 final int keyCount = keys.length;
701 for (int i = 0; i < keyCount; i++) {
702 final Key key = keys[i];
703 if (drawSingleKey && invalidKey != key) {
704 continue;
705 }
706 int[] drawableState = key.getCurrentDrawableState();
707 keyBackground.setState(drawableState);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700708
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 // Switch the character to uppercase if shift is pressed
710 String label = key.label == null? null : adjustCase(key.label).toString();
John Reckd0374c62015-10-20 13:25:01 -0700711
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 final Rect bounds = keyBackground.getBounds();
John Reckd0374c62015-10-20 13:25:01 -0700713 if (key.width != bounds.right ||
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 key.height != bounds.bottom) {
715 keyBackground.setBounds(0, 0, key.width, key.height);
716 }
717 canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
718 keyBackground.draw(canvas);
John Reckd0374c62015-10-20 13:25:01 -0700719
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800720 if (label != null) {
721 // For characters, use large font. For labels like "Done", use small font.
722 if (label.length() > 1 && key.codes.length < 2) {
723 paint.setTextSize(mLabelTextSize);
724 paint.setTypeface(Typeface.DEFAULT_BOLD);
725 } else {
726 paint.setTextSize(mKeyTextSize);
727 paint.setTypeface(Typeface.DEFAULT);
728 }
729 // Draw a drop shadow for the text
730 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
731 // Draw the text
732 canvas.drawText(label,
733 (key.width - padding.left - padding.right) / 2
734 + padding.left,
735 (key.height - padding.top - padding.bottom) / 2
736 + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
737 paint);
738 // Turn off drop shadow
739 paint.setShadowLayer(0, 0, 0, 0);
740 } else if (key.icon != null) {
John Reckd0374c62015-10-20 13:25:01 -0700741 final int drawableX = (key.width - padding.left - padding.right
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 - key.icon.getIntrinsicWidth()) / 2 + padding.left;
John Reckd0374c62015-10-20 13:25:01 -0700743 final int drawableY = (key.height - padding.top - padding.bottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 - key.icon.getIntrinsicHeight()) / 2 + padding.top;
745 canvas.translate(drawableX, drawableY);
John Reckd0374c62015-10-20 13:25:01 -0700746 key.icon.setBounds(0, 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
748 key.icon.draw(canvas);
749 canvas.translate(-drawableX, -drawableY);
750 }
751 canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
752 }
753 mInvalidatedKey = null;
754 // Overlay a dark rectangle to dim the keyboard
755 if (mMiniKeyboardOnScreen) {
756 paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
757 canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
758 }
759
760 if (DEBUG && mShowTouchPoints) {
761 paint.setAlpha(128);
762 paint.setColor(0xFFFF0000);
763 canvas.drawCircle(mStartX, mStartY, 3, paint);
764 canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
765 paint.setColor(0xFF0000FF);
766 canvas.drawCircle(mLastX, mLastY, 3, paint);
767 paint.setColor(0xFF00FF00);
768 canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
769 }
Tarandeep Singhd59c6952018-05-18 13:53:53 -0700770 mCanvas.restore();
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700771 mDrawPending = false;
772 mDirtyRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 }
774
775 private int getKeyIndices(int x, int y, int[] allKeys) {
776 final Key[] keys = mKeys;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800777 int primaryIndex = NOT_A_KEY;
778 int closestKey = NOT_A_KEY;
779 int closestKeyDist = mProximityThreshold + 1;
780 java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
781 int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
782 final int keyCount = nearestKeyIndices.length;
783 for (int i = 0; i < keyCount; i++) {
784 final Key key = keys[nearestKeyIndices[i]];
785 int dist = 0;
786 boolean isInside = key.isInside(x,y);
Amith Yamasaniec5df832010-02-08 15:24:57 -0800787 if (isInside) {
788 primaryIndex = nearestKeyIndices[i];
789 }
790
John Reckd0374c62015-10-20 13:25:01 -0700791 if (((mProximityCorrectOn
792 && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 || isInside)
794 && key.codes[0] > 32) {
795 // Find insertion point
796 final int nCodes = key.codes.length;
797 if (dist < closestKeyDist) {
798 closestKeyDist = dist;
799 closestKey = nearestKeyIndices[i];
800 }
John Reckd0374c62015-10-20 13:25:01 -0700801
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 if (allKeys == null) continue;
John Reckd0374c62015-10-20 13:25:01 -0700803
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 for (int j = 0; j < mDistances.length; j++) {
805 if (mDistances[j] > dist) {
806 // Make space for nCodes codes
807 System.arraycopy(mDistances, j, mDistances, j + nCodes,
808 mDistances.length - j - nCodes);
809 System.arraycopy(allKeys, j, allKeys, j + nCodes,
810 allKeys.length - j - nCodes);
811 for (int c = 0; c < nCodes; c++) {
812 allKeys[j + c] = key.codes[c];
813 mDistances[j + c] = dist;
814 }
815 break;
816 }
817 }
818 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 }
820 if (primaryIndex == NOT_A_KEY) {
821 primaryIndex = closestKey;
822 }
823 return primaryIndex;
824 }
825
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700826 private void detectAndSendKey(int index, int x, int y, long eventTime) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 if (index != NOT_A_KEY && index < mKeys.length) {
828 final Key key = mKeys[index];
829 if (key.text != null) {
830 mKeyboardActionListener.onText(key.text);
831 mKeyboardActionListener.onRelease(NOT_A_KEY);
832 } else {
833 int code = key.codes[0];
834 //TextEntryState.keyPressedAt(key, x, y);
835 int[] codes = new int[MAX_NEARBY_KEYS];
836 Arrays.fill(codes, NOT_A_KEY);
837 getKeyIndices(x, y, codes);
838 // Multi-tap
839 if (mInMultiTap) {
840 if (mTapCount != -1) {
841 mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
842 } else {
843 mTapCount = 0;
844 }
845 code = key.codes[mTapCount];
846 }
847 mKeyboardActionListener.onKey(code, codes);
848 mKeyboardActionListener.onRelease(code);
849 }
850 mLastSentIndex = index;
851 mLastTapTime = eventTime;
852 }
853 }
854
855 /**
856 * Handle multi-tap keys by producing the key label for the current multi-tap state.
857 */
858 private CharSequence getPreviewText(Key key) {
859 if (mInMultiTap) {
860 // Multi-tap
861 mPreviewLabel.setLength(0);
862 mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
863 return adjustCase(mPreviewLabel);
864 } else {
865 return adjustCase(key.label);
866 }
867 }
John Reckd0374c62015-10-20 13:25:01 -0700868
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 private void showPreview(int keyIndex) {
870 int oldKeyIndex = mCurrentKeyIndex;
871 final PopupWindow previewPopup = mPreviewPopup;
John Reckd0374c62015-10-20 13:25:01 -0700872
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 mCurrentKeyIndex = keyIndex;
874 // Release the old key and press the new key
875 final Key[] keys = mKeys;
876 if (oldKeyIndex != mCurrentKeyIndex) {
877 if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700878 Key oldKey = keys[oldKeyIndex];
879 oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 invalidateKey(oldKeyIndex);
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700881 final int keyCode = oldKey.codes[0];
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700882 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700883 keyCode);
884 // TODO: We need to implement AccessibilityNodeProvider for this view.
885 sendAccessibilityEventForUnicodeCharacter(
886 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 }
888 if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700889 Key newKey = keys[mCurrentKeyIndex];
890 newKey.onPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 invalidateKey(mCurrentKeyIndex);
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700892 final int keyCode = newKey.codes[0];
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700893 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700894 keyCode);
895 // TODO: We need to implement AccessibilityNodeProvider for this view.
896 sendAccessibilityEventForUnicodeCharacter(
897 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 }
899 }
900 // If key changed and preview is on ...
901 if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
902 mHandler.removeMessages(MSG_SHOW_PREVIEW);
903 if (previewPopup.isShowing()) {
904 if (keyIndex == NOT_A_KEY) {
905 mHandler.sendMessageDelayed(mHandler
John Reckd0374c62015-10-20 13:25:01 -0700906 .obtainMessage(MSG_REMOVE_PREVIEW),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 DELAY_AFTER_PREVIEW);
908 }
909 }
910 if (keyIndex != NOT_A_KEY) {
911 if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
912 // Show right away, if it's already visible and finger is moving around
913 showKey(keyIndex);
914 } else {
915 mHandler.sendMessageDelayed(
John Reckd0374c62015-10-20 13:25:01 -0700916 mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917 DELAY_BEFORE_PREVIEW);
918 }
919 }
920 }
921 }
John Reckd0374c62015-10-20 13:25:01 -0700922
Mathew Inwood1dd7d112018-07-31 14:53:29 +0100923 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 private void showKey(final int keyIndex) {
925 final PopupWindow previewPopup = mPreviewPopup;
926 final Key[] keys = mKeys;
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700927 if (keyIndex < 0 || keyIndex >= mKeys.length) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 Key key = keys[keyIndex];
929 if (key.icon != null) {
John Reckd0374c62015-10-20 13:25:01 -0700930 mPreviewText.setCompoundDrawables(null, null, null,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 key.iconPreview != null ? key.iconPreview : key.icon);
932 mPreviewText.setText(null);
933 } else {
934 mPreviewText.setCompoundDrawables(null, null, null, null);
935 mPreviewText.setText(getPreviewText(key));
936 if (key.label.length() > 1 && key.codes.length < 2) {
Amith Yamasanie4037002009-07-23 17:38:15 -0700937 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
939 } else {
Amith Yamasanie4037002009-07-23 17:38:15 -0700940 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 mPreviewText.setTypeface(Typeface.DEFAULT);
942 }
943 }
John Reckd0374c62015-10-20 13:25:01 -0700944 mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
John Reckd0374c62015-10-20 13:25:01 -0700946 int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
948 final int popupHeight = mPreviewHeight;
949 LayoutParams lp = mPreviewText.getLayoutParams();
950 if (lp != null) {
951 lp.width = popupWidth;
952 lp.height = popupHeight;
953 }
954 if (!mPreviewCentered) {
955 mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
956 mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
957 } else {
958 // TODO: Fix this if centering is brought back
959 mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
960 mPopupPreviewY = - mPreviewText.getMeasuredHeight();
961 }
962 mHandler.removeMessages(MSG_REMOVE_PREVIEW);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900963 getLocationInWindow(mCoordinates);
964 mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
965 mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
966
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800967 // Set the preview background state
968 mPreviewText.getBackground().setState(
969 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900970 mPopupPreviewX += mCoordinates[0];
971 mPopupPreviewY += mCoordinates[1];
Amith Yamasani42973a42010-03-19 17:21:44 -0700972
973 // If the popup cannot be shown above the key, put it on the side
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900974 getLocationOnScreen(mCoordinates);
975 if (mPopupPreviewY + mCoordinates[1] < 0) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700976 // If the key you're pressing is on the left side of the keyboard, show the popup on
977 // the right, offset by enough to see at least one key to the left/right.
978 if (key.x + key.width <= getWidth() / 2) {
979 mPopupPreviewX += (int) (key.width * 2.5);
980 } else {
981 mPopupPreviewX -= (int) (key.width * 2.5);
982 }
983 mPopupPreviewY += popupHeight;
984 }
985
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800986 if (previewPopup.isShowing()) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700987 previewPopup.update(mPopupPreviewX, mPopupPreviewY,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 popupWidth, popupHeight);
989 } else {
990 previewPopup.setWidth(popupWidth);
991 previewPopup.setHeight(popupHeight);
John Reckd0374c62015-10-20 13:25:01 -0700992 previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
Amith Yamasani42973a42010-03-19 17:21:44 -0700993 mPopupPreviewX, mPopupPreviewY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 }
995 mPreviewText.setVisibility(VISIBLE);
996 }
997
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700998 private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700999 if (mAccessibilityManager.isEnabled()) {
1000 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
1001 onInitializeAccessibilityEvent(event);
Phil Weaver385912e2017-02-10 10:06:56 -08001002 final String text;
1003 switch (code) {
1004 case Keyboard.KEYCODE_ALT:
1005 text = mContext.getString(R.string.keyboardview_keycode_alt);
1006 break;
1007 case Keyboard.KEYCODE_CANCEL:
1008 text = mContext.getString(R.string.keyboardview_keycode_cancel);
1009 break;
1010 case Keyboard.KEYCODE_DELETE:
1011 text = mContext.getString(R.string.keyboardview_keycode_delete);
1012 break;
1013 case Keyboard.KEYCODE_DONE:
1014 text = mContext.getString(R.string.keyboardview_keycode_done);
1015 break;
1016 case Keyboard.KEYCODE_MODE_CHANGE:
1017 text = mContext.getString(R.string.keyboardview_keycode_mode_change);
1018 break;
1019 case Keyboard.KEYCODE_SHIFT:
1020 text = mContext.getString(R.string.keyboardview_keycode_shift);
1021 break;
1022 case '\n':
1023 text = mContext.getString(R.string.keyboardview_keycode_enter);
1024 break;
1025 default:
1026 text = String.valueOf((char) code);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001027 }
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001028 event.getText().add(text);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001029 mAccessibilityManager.sendAccessibilityEvent(event);
1030 }
1031 }
1032
Amith Yamasanif04da952009-05-06 15:46:00 -07001033 /**
1034 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
John Reckd0374c62015-10-20 13:25:01 -07001035 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
Amith Yamasanif04da952009-05-06 15:46:00 -07001036 * draws the cached buffer.
1037 * @see #invalidateKey(int)
1038 */
1039 public void invalidateAllKeys() {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001040 mDirtyRect.union(0, 0, getWidth(), getHeight());
1041 mDrawPending = true;
1042 invalidate();
1043 }
Amith Yamasanif04da952009-05-06 15:46:00 -07001044
1045 /**
1046 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1047 * one key is changing it's content. Any changes that affect the position or size of the key
1048 * may not be honored.
1049 * @param keyIndex the index of the key in the attached {@link Keyboard}.
1050 * @see #invalidateAllKeys
1051 */
1052 public void invalidateKey(int keyIndex) {
1053 if (mKeys == null) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001054 if (keyIndex < 0 || keyIndex >= mKeys.length) {
1055 return;
1056 }
1057 final Key key = mKeys[keyIndex];
1058 mInvalidatedKey = key;
John Reckd0374c62015-10-20 13:25:01 -07001059 mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001060 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1061 onBufferDraw();
John Reckd0374c62015-10-20 13:25:01 -07001062 invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1064 }
1065
Mathew Inwood1dd7d112018-07-31 14:53:29 +01001066 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 private boolean openPopupIfRequired(MotionEvent me) {
1068 // Check if we have a popup layout specified first.
1069 if (mPopupLayout == 0) {
1070 return false;
1071 }
1072 if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
1073 return false;
1074 }
1075
John Reckd0374c62015-10-20 13:25:01 -07001076 Key popupKey = mKeys[mCurrentKey];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 boolean result = onLongPress(popupKey);
1078 if (result) {
1079 mAbortKey = true;
1080 showPreview(NOT_A_KEY);
1081 }
1082 return result;
1083 }
1084
1085 /**
1086 * Called when a key is long pressed. By default this will open any popup keyboard associated
1087 * with this key through the attributes popupLayout and popupCharacters.
1088 * @param popupKey the key that was long pressed
1089 * @return true if the long press is handled, false otherwise. Subclasses should call the
1090 * method on the base class if the subclass doesn't wish to handle the call.
1091 */
1092 protected boolean onLongPress(Key popupKey) {
1093 int popupKeyboardId = popupKey.popupResId;
1094
1095 if (popupKeyboardId != 0) {
1096 mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
1097 if (mMiniKeyboardContainer == null) {
1098 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
1099 Context.LAYOUT_INFLATER_SERVICE);
1100 mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
1101 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1102 com.android.internal.R.id.keyboardView);
1103 View closeButton = mMiniKeyboardContainer.findViewById(
Dianne Hackborn88fb1062009-03-27 14:59:22 -07001104 com.android.internal.R.id.closeButton);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 if (closeButton != null) closeButton.setOnClickListener(this);
1106 mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
1107 public void onKey(int primaryCode, int[] keyCodes) {
1108 mKeyboardActionListener.onKey(primaryCode, keyCodes);
1109 dismissPopupKeyboard();
1110 }
John Reckd0374c62015-10-20 13:25:01 -07001111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112 public void onText(CharSequence text) {
1113 mKeyboardActionListener.onText(text);
1114 dismissPopupKeyboard();
1115 }
John Reckd0374c62015-10-20 13:25:01 -07001116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001117 public void swipeLeft() { }
1118 public void swipeRight() { }
1119 public void swipeUp() { }
1120 public void swipeDown() { }
1121 public void onPress(int primaryCode) {
1122 mKeyboardActionListener.onPress(primaryCode);
1123 }
1124 public void onRelease(int primaryCode) {
1125 mKeyboardActionListener.onRelease(primaryCode);
1126 }
1127 });
1128 //mInputView.setSuggest(mSuggest);
1129 Keyboard keyboard;
1130 if (popupKey.popupCharacters != null) {
John Reckd0374c62015-10-20 13:25:01 -07001131 keyboard = new Keyboard(getContext(), popupKeyboardId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
1133 } else {
1134 keyboard = new Keyboard(getContext(), popupKeyboardId);
1135 }
1136 mMiniKeyboard.setKeyboard(keyboard);
1137 mMiniKeyboard.setPopupParent(this);
1138 mMiniKeyboardContainer.measure(
John Reckd0374c62015-10-20 13:25:01 -07001139 MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001140 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
John Reckd0374c62015-10-20 13:25:01 -07001141
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
1143 } else {
1144 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1145 com.android.internal.R.id.keyboardView);
1146 }
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001147 getLocationInWindow(mCoordinates);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 mPopupX = popupKey.x + mPaddingLeft;
1149 mPopupY = popupKey.y + mPaddingTop;
1150 mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
1151 mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001152 final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
1153 final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
1155 mMiniKeyboard.setShifted(isShifted());
1156 mPopupKeyboard.setContentView(mMiniKeyboardContainer);
1157 mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
1158 mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
1159 mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
1160 mMiniKeyboardOnScreen = true;
1161 //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
Amith Yamasanif04da952009-05-06 15:46:00 -07001162 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 return true;
1164 }
1165 return false;
1166 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001167
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001168 @Override
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001169 public boolean onHoverEvent(MotionEvent event) {
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001170 if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001171 final int action = event.getAction();
1172 switch (action) {
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -07001173 case MotionEvent.ACTION_HOVER_ENTER: {
1174 event.setAction(MotionEvent.ACTION_DOWN);
1175 } break;
1176 case MotionEvent.ACTION_HOVER_MOVE: {
1177 event.setAction(MotionEvent.ACTION_MOVE);
1178 } break;
1179 case MotionEvent.ACTION_HOVER_EXIT: {
1180 event.setAction(MotionEvent.ACTION_UP);
1181 } break;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001182 }
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -07001183 return onTouchEvent(event);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001184 }
Svetoslav Ganov67578612011-11-01 16:29:45 -07001185 return true;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001186 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001187
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188 @Override
Svetoslav Ganov67578612011-11-01 16:29:45 -07001189 public boolean onTouchEvent(MotionEvent me) {
John Reckd0374c62015-10-20 13:25:01 -07001190 // Convert multi-pointer up/down events to single up/down events to
Amith Yamasanie877ec62009-08-05 21:12:07 -07001191 // deal with the typical multi-pointer behavior of two-thumb typing
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001192 final int pointerCount = me.getPointerCount();
1193 final int action = me.getAction();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001194 boolean result = false;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001195 final long now = me.getEventTime();
1196
Amith Yamasanie877ec62009-08-05 21:12:07 -07001197 if (pointerCount != mOldPointerCount) {
Amith Yamasanie877ec62009-08-05 21:12:07 -07001198 if (pointerCount == 1) {
1199 // Send a down event for the latest pointer
1200 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
1201 me.getX(), me.getY(), me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001202 result = onModifiedTouchEvent(down, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001203 down.recycle();
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001204 // If it's an up action, then deliver the up as well.
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001205 if (action == MotionEvent.ACTION_UP) {
1206 result = onModifiedTouchEvent(me, true);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001207 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001208 } else {
1209 // Send an up event for the last pointer
1210 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
1211 mOldPointerX, mOldPointerY, me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001212 result = onModifiedTouchEvent(up, true);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001213 up.recycle();
1214 }
1215 } else {
1216 if (pointerCount == 1) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001217 result = onModifiedTouchEvent(me, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001218 mOldPointerX = me.getX();
1219 mOldPointerY = me.getY();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001220 } else {
1221 // Don't do anything when 2 pointers are down and moving.
1222 result = true;
1223 }
1224 }
1225 mOldPointerCount = pointerCount;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001226
Amith Yamasanie877ec62009-08-05 21:12:07 -07001227 return result;
1228 }
1229
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001230 private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
Amith Yamasani678cdbe2009-10-27 11:45:08 -07001231 int touchX = (int) me.getX() - mPaddingLeft;
Tadashi G. Takaoka0972d072010-07-30 15:18:56 -07001232 int touchY = (int) me.getY() - mPaddingTop;
1233 if (touchY >= -mVerticalCorrection)
1234 touchY += mVerticalCorrection;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001235 final int action = me.getAction();
1236 final long eventTime = me.getEventTime();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 int keyIndex = getKeyIndices(touchX, touchY, null);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001238 mPossiblePoly = possiblePoly;
1239
1240 // Track the last few movements to look for spurious swipes.
1241 if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
1242 mSwipeTracker.addMovement(me);
1243
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001244 // Ignore all motion events until a DOWN.
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001245 if (mAbortKey
1246 && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001247 return true;
1248 }
1249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 if (mGestureDetector.onTouchEvent(me)) {
1251 showPreview(NOT_A_KEY);
1252 mHandler.removeMessages(MSG_REPEAT);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001253 mHandler.removeMessages(MSG_LONGPRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 return true;
1255 }
John Reckd0374c62015-10-20 13:25:01 -07001256
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001257 // Needs to be called after the gesture detector gets a turn, as it may have
1258 // displayed the mini keyboard
Amith Yamasani0743a382009-12-08 14:18:19 -08001259 if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 return true;
1261 }
1262
1263 switch (action) {
1264 case MotionEvent.ACTION_DOWN:
1265 mAbortKey = false;
1266 mStartX = touchX;
1267 mStartY = touchY;
1268 mLastCodeX = touchX;
1269 mLastCodeY = touchY;
1270 mLastKeyTime = 0;
1271 mCurrentKeyTime = 0;
1272 mLastKey = NOT_A_KEY;
1273 mCurrentKey = keyIndex;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001274 mDownKey = keyIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001275 mDownTime = me.getEventTime();
1276 mLastMoveTime = mDownTime;
1277 checkMultiTap(eventTime, keyIndex);
John Reckd0374c62015-10-20 13:25:01 -07001278 mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279 mKeys[keyIndex].codes[0] : 0);
1280 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
1281 mRepeatKeyIndex = mCurrentKey;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 Message msg = mHandler.obtainMessage(MSG_REPEAT);
1283 mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001284 repeatKey();
1285 // Delivering the key could have caused an abort
1286 if (mAbortKey) {
1287 mRepeatKeyIndex = NOT_A_KEY;
1288 break;
1289 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290 }
1291 if (mCurrentKey != NOT_A_KEY) {
1292 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1293 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1294 }
1295 showPreview(keyIndex);
1296 break;
1297
1298 case MotionEvent.ACTION_MOVE:
1299 boolean continueLongPress = false;
1300 if (keyIndex != NOT_A_KEY) {
1301 if (mCurrentKey == NOT_A_KEY) {
1302 mCurrentKey = keyIndex;
1303 mCurrentKeyTime = eventTime - mDownTime;
1304 } else {
1305 if (keyIndex == mCurrentKey) {
1306 mCurrentKeyTime += eventTime - mLastMoveTime;
1307 continueLongPress = true;
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001308 } else if (mRepeatKeyIndex == NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 resetMultiTap();
1310 mLastKey = mCurrentKey;
1311 mLastCodeX = mLastX;
1312 mLastCodeY = mLastY;
1313 mLastKeyTime =
1314 mCurrentKeyTime + eventTime - mLastMoveTime;
1315 mCurrentKey = keyIndex;
1316 mCurrentKeyTime = 0;
1317 }
1318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 }
1320 if (!continueLongPress) {
1321 // Cancel old longpress
1322 mHandler.removeMessages(MSG_LONGPRESS);
1323 // Start new longpress if key has changed
1324 if (keyIndex != NOT_A_KEY) {
1325 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1326 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1327 }
1328 }
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001329 showPreview(mCurrentKey);
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001330 mLastMoveTime = eventTime;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001331 break;
1332
1333 case MotionEvent.ACTION_UP:
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001334 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 if (keyIndex == mCurrentKey) {
1336 mCurrentKeyTime += eventTime - mLastMoveTime;
1337 } else {
1338 resetMultiTap();
1339 mLastKey = mCurrentKey;
1340 mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
1341 mCurrentKey = keyIndex;
1342 mCurrentKeyTime = 0;
1343 }
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001344 if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
1345 && mLastKey != NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 mCurrentKey = mLastKey;
1347 touchX = mLastCodeX;
1348 touchY = mLastCodeY;
1349 }
1350 showPreview(NOT_A_KEY);
1351 Arrays.fill(mKeyIndices, NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 // If we're not on a repeating key (which sends on a DOWN event)
1353 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001354 detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001356 invalidateKey(keyIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001357 mRepeatKeyIndex = NOT_A_KEY;
1358 break;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001359 case MotionEvent.ACTION_CANCEL:
1360 removeMessages();
Amith Yamasani0743a382009-12-08 14:18:19 -08001361 dismissPopupKeyboard();
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001362 mAbortKey = true;
1363 showPreview(NOT_A_KEY);
1364 invalidateKey(mCurrentKey);
1365 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001366 }
1367 mLastX = touchX;
1368 mLastY = touchY;
1369 return true;
1370 }
1371
Mathew Inwood1dd7d112018-07-31 14:53:29 +01001372 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 private boolean repeatKey() {
1374 Key key = mKeys[mRepeatKeyIndex];
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001375 detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 return true;
1377 }
John Reckd0374c62015-10-20 13:25:01 -07001378
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001379 protected void swipeRight() {
1380 mKeyboardActionListener.swipeRight();
1381 }
John Reckd0374c62015-10-20 13:25:01 -07001382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001383 protected void swipeLeft() {
1384 mKeyboardActionListener.swipeLeft();
1385 }
1386
1387 protected void swipeUp() {
1388 mKeyboardActionListener.swipeUp();
1389 }
1390
1391 protected void swipeDown() {
1392 mKeyboardActionListener.swipeDown();
1393 }
1394
1395 public void closing() {
1396 if (mPreviewPopup.isShowing()) {
1397 mPreviewPopup.dismiss();
1398 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001399 removeMessages();
John Reckd0374c62015-10-20 13:25:01 -07001400
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001401 dismissPopupKeyboard();
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001402 mBuffer = null;
1403 mCanvas = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404 mMiniKeyboardCache.clear();
1405 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001406
1407 private void removeMessages() {
Clara Bayarri5ba31aa2015-10-23 13:22:14 +01001408 if (mHandler != null) {
1409 mHandler.removeMessages(MSG_REPEAT);
1410 mHandler.removeMessages(MSG_LONGPRESS);
1411 mHandler.removeMessages(MSG_SHOW_PREVIEW);
1412 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001413 }
1414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 @Override
1416 public void onDetachedFromWindow() {
1417 super.onDetachedFromWindow();
1418 closing();
1419 }
1420
1421 private void dismissPopupKeyboard() {
1422 if (mPopupKeyboard.isShowing()) {
1423 mPopupKeyboard.dismiss();
1424 mMiniKeyboardOnScreen = false;
Amith Yamasanif04da952009-05-06 15:46:00 -07001425 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426 }
1427 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001428
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001429 public boolean handleBack() {
1430 if (mPopupKeyboard.isShowing()) {
1431 dismissPopupKeyboard();
1432 return true;
1433 }
1434 return false;
1435 }
1436
1437 private void resetMultiTap() {
1438 mLastSentIndex = NOT_A_KEY;
1439 mTapCount = 0;
1440 mLastTapTime = -1;
1441 mInMultiTap = false;
1442 }
John Reckd0374c62015-10-20 13:25:01 -07001443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001444 private void checkMultiTap(long eventTime, int keyIndex) {
1445 if (keyIndex == NOT_A_KEY) return;
1446 Key key = mKeys[keyIndex];
1447 if (key.codes.length > 1) {
1448 mInMultiTap = true;
1449 if (eventTime < mLastTapTime + MULTITAP_INTERVAL
1450 && keyIndex == mLastSentIndex) {
1451 mTapCount = (mTapCount + 1) % key.codes.length;
1452 return;
1453 } else {
1454 mTapCount = -1;
1455 return;
1456 }
1457 }
1458 if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
1459 resetMultiTap();
1460 }
1461 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001462
1463 private static class SwipeTracker {
1464
1465 static final int NUM_PAST = 4;
1466 static final int LONGEST_PAST_TIME = 200;
1467
1468 final float mPastX[] = new float[NUM_PAST];
1469 final float mPastY[] = new float[NUM_PAST];
1470 final long mPastTime[] = new long[NUM_PAST];
1471
1472 float mYVelocity;
1473 float mXVelocity;
1474
1475 public void clear() {
1476 mPastTime[0] = 0;
1477 }
1478
1479 public void addMovement(MotionEvent ev) {
1480 long time = ev.getEventTime();
1481 final int N = ev.getHistorySize();
1482 for (int i=0; i<N; i++) {
1483 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
1484 ev.getHistoricalEventTime(i));
1485 }
1486 addPoint(ev.getX(), ev.getY(), time);
1487 }
1488
1489 private void addPoint(float x, float y, long time) {
1490 int drop = -1;
1491 int i;
1492 final long[] pastTime = mPastTime;
1493 for (i=0; i<NUM_PAST; i++) {
1494 if (pastTime[i] == 0) {
1495 break;
1496 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
1497 drop = i;
1498 }
1499 }
1500 if (i == NUM_PAST && drop < 0) {
1501 drop = 0;
1502 }
1503 if (drop == i) drop--;
1504 final float[] pastX = mPastX;
1505 final float[] pastY = mPastY;
1506 if (drop >= 0) {
1507 final int start = drop+1;
1508 final int count = NUM_PAST-drop-1;
1509 System.arraycopy(pastX, start, pastX, 0, count);
1510 System.arraycopy(pastY, start, pastY, 0, count);
1511 System.arraycopy(pastTime, start, pastTime, 0, count);
1512 i -= (drop+1);
1513 }
1514 pastX[i] = x;
1515 pastY[i] = y;
1516 pastTime[i] = time;
1517 i++;
1518 if (i < NUM_PAST) {
1519 pastTime[i] = 0;
1520 }
1521 }
1522
1523 public void computeCurrentVelocity(int units) {
1524 computeCurrentVelocity(units, Float.MAX_VALUE);
1525 }
1526
1527 public void computeCurrentVelocity(int units, float maxVelocity) {
1528 final float[] pastX = mPastX;
1529 final float[] pastY = mPastY;
1530 final long[] pastTime = mPastTime;
1531
1532 final float oldestX = pastX[0];
1533 final float oldestY = pastY[0];
1534 final long oldestTime = pastTime[0];
1535 float accumX = 0;
1536 float accumY = 0;
1537 int N=0;
1538 while (N < NUM_PAST) {
1539 if (pastTime[N] == 0) {
1540 break;
1541 }
1542 N++;
1543 }
1544
1545 for (int i=1; i < N; i++) {
1546 final int dur = (int)(pastTime[i] - oldestTime);
1547 if (dur == 0) continue;
1548 float dist = pastX[i] - oldestX;
1549 float vel = (dist/dur) * units; // pixels/frame.
1550 if (accumX == 0) accumX = vel;
1551 else accumX = (accumX + vel) * .5f;
1552
1553 dist = pastY[i] - oldestY;
1554 vel = (dist/dur) * units; // pixels/frame.
1555 if (accumY == 0) accumY = vel;
1556 else accumY = (accumY + vel) * .5f;
1557 }
1558 mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
1559 : Math.min(accumX, maxVelocity);
1560 mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
1561 : Math.min(accumY, maxVelocity);
1562 }
1563
1564 public float getXVelocity() {
1565 return mXVelocity;
1566 }
1567
1568 public float getYVelocity() {
1569 return mYVelocity;
1570 }
1571 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001572}