blob: d1000ade1ed0de71d34f249f042977b9d85344a2 [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.res.TypedArray;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070021import android.graphics.Bitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.graphics.Canvas;
23import android.graphics.Paint;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070024import android.graphics.PorterDuff;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.graphics.Rect;
Svetoslav Ganov0d67c892011-08-06 08:06:46 -070026import android.graphics.Typeface;
Svetoslav Ganov70934932011-08-17 05:16:17 -070027import android.graphics.Paint.Align;
28import android.graphics.Region.Op;
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;
Alan Viveretteb6e0cb92014-11-24 15:13:43 -080034import android.os.UserHandle;
Svetoslav Ganov55f937a2011-12-05 11:42:07 -080035import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.util.AttributeSet;
Amith Yamasanie4037002009-07-23 17:38:15 -070037import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.view.GestureDetector;
39import android.view.Gravity;
40import android.view.LayoutInflater;
41import android.view.MotionEvent;
42import android.view.View;
Amith Yamasanib974c7a2009-07-21 15:05:39 -070043import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.ViewGroup.LayoutParams;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -070045import android.view.accessibility.AccessibilityEvent;
46import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.widget.PopupWindow;
48import android.widget.TextView;
49
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070050import com.android.internal.R;
51
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import java.util.Arrays;
53import java.util.HashMap;
54import java.util.List;
55import java.util.Map;
56
57/**
58 * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
59 * detecting key presses and touch movements.
John Reckd0374c62015-10-20 13:25:01 -070060 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 * @attr ref android.R.styleable#KeyboardView_keyBackground
62 * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
63 * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
64 * @attr ref android.R.styleable#KeyboardView_labelTextSize
65 * @attr ref android.R.styleable#KeyboardView_keyTextSize
66 * @attr ref android.R.styleable#KeyboardView_keyTextColor
67 * @attr ref android.R.styleable#KeyboardView_verticalCorrection
68 * @attr ref android.R.styleable#KeyboardView_popupLayout
69 */
70public class KeyboardView extends View implements View.OnClickListener {
71
72 /**
73 * Listener for virtual keyboard events.
74 */
75 public interface OnKeyboardActionListener {
John Reckd0374c62015-10-20 13:25:01 -070076
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 /**
78 * Called when the user presses a key. This is sent before the {@link #onKey} is called.
79 * For keys that repeat, this is only called once.
80 * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
81 * key, the value will be zero.
82 */
83 void onPress(int primaryCode);
John Reckd0374c62015-10-20 13:25:01 -070084
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 /**
86 * Called when the user releases a key. This is sent after the {@link #onKey} is called.
87 * For keys that repeat, this is only called once.
88 * @param primaryCode the code of the key that was released
89 */
90 void onRelease(int primaryCode);
91
92 /**
93 * Send a key press to the listener.
94 * @param primaryCode this is the key that was pressed
95 * @param keyCodes the codes for all the possible alternative keys
96 * with the primary code being the first. If the primary key code is
97 * a single character such as an alphabet or number or symbol, the alternatives
98 * will include other characters that may be on the same key or adjacent keys.
99 * These codes are useful to correct for accidental presses of a key adjacent to
100 * the intended key.
101 */
102 void onKey(int primaryCode, int[] keyCodes);
103
104 /**
105 * Sends a sequence of characters to the listener.
106 * @param text the sequence of characters to be displayed.
107 */
108 void onText(CharSequence text);
John Reckd0374c62015-10-20 13:25:01 -0700109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 /**
111 * Called when the user quickly moves the finger from right to left.
112 */
113 void swipeLeft();
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 left to right.
117 */
118 void swipeRight();
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 up to down.
122 */
123 void swipeDown();
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 down to up.
127 */
128 void swipeUp();
129 }
130
131 private static final boolean DEBUG = false;
132 private static final int NOT_A_KEY = -1;
133 private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
John Reckd0374c62015-10-20 13:25:01 -0700134 private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 private Keyboard mKeyboard;
137 private int mCurrentKeyIndex = NOT_A_KEY;
138 private int mLabelTextSize;
139 private int mKeyTextSize;
140 private int mKeyTextColor;
141 private float mShadowRadius;
142 private int mShadowColor;
143 private float mBackgroundDimAmount;
John Reckd0374c62015-10-20 13:25:01 -0700144
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 private TextView mPreviewText;
146 private PopupWindow mPreviewPopup;
147 private int mPreviewTextSizeLarge;
148 private int mPreviewOffset;
149 private int mPreviewHeight;
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900150 // Working variable
151 private final int[] mCoordinates = new int[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152
153 private PopupWindow mPopupKeyboard;
154 private View mMiniKeyboardContainer;
155 private KeyboardView mMiniKeyboard;
156 private boolean mMiniKeyboardOnScreen;
157 private View mPopupParent;
158 private int mMiniKeyboardOffsetX;
159 private int mMiniKeyboardOffsetY;
160 private Map<Key,View> mMiniKeyboardCache;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 private Key[] mKeys;
162
163 /** Listener for {@link OnKeyboardActionListener}. */
164 private OnKeyboardActionListener mKeyboardActionListener;
John Reckd0374c62015-10-20 13:25:01 -0700165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 private static final int MSG_SHOW_PREVIEW = 1;
167 private static final int MSG_REMOVE_PREVIEW = 2;
168 private static final int MSG_REPEAT = 3;
169 private static final int MSG_LONGPRESS = 4;
Amith Yamasanie4037002009-07-23 17:38:15 -0700170
Amith Yamasani29d85df2009-08-11 16:42:51 -0700171 private static final int DELAY_BEFORE_PREVIEW = 0;
172 private static final int DELAY_AFTER_PREVIEW = 70;
Amith Yamasani7aa936e2010-02-11 18:51:25 -0800173 private static final int DEBOUNCE_TIME = 70;
John Reckd0374c62015-10-20 13:25:01 -0700174
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 private int mVerticalCorrection;
176 private int mProximityThreshold;
177
178 private boolean mPreviewCentered = false;
179 private boolean mShowPreview = true;
180 private boolean mShowTouchPoints = true;
181 private int mPopupPreviewX;
182 private int mPopupPreviewY;
183
184 private int mLastX;
185 private int mLastY;
186 private int mStartX;
187 private int mStartY;
188
189 private boolean mProximityCorrectOn;
John Reckd0374c62015-10-20 13:25:01 -0700190
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 private Paint mPaint;
192 private Rect mPadding;
John Reckd0374c62015-10-20 13:25:01 -0700193
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 private long mDownTime;
195 private long mLastMoveTime;
196 private int mLastKey;
197 private int mLastCodeX;
198 private int mLastCodeY;
199 private int mCurrentKey = NOT_A_KEY;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700200 private int mDownKey = NOT_A_KEY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 private long mLastKeyTime;
202 private long mCurrentKeyTime;
203 private int[] mKeyIndices = new int[12];
204 private GestureDetector mGestureDetector;
205 private int mPopupX;
206 private int mPopupY;
207 private int mRepeatKeyIndex = NOT_A_KEY;
208 private int mPopupLayout;
209 private boolean mAbortKey;
210 private Key mInvalidatedKey;
211 private Rect mClipRegion = new Rect(0, 0, 0, 0);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700212 private boolean mPossiblePoly;
213 private SwipeTracker mSwipeTracker = new SwipeTracker();
214 private int mSwipeThreshold;
215 private boolean mDisambiguateSwipe;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700216
217 // Variables for dealing with multiple pointers
218 private int mOldPointerCount = 1;
219 private float mOldPointerX;
220 private float mOldPointerY;
221
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 private Drawable mKeyBackground;
223
224 private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
225 private static final int REPEAT_START_DELAY = 400;
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700226 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227
228 private static int MAX_NEARBY_KEYS = 12;
229 private int[] mDistances = new int[MAX_NEARBY_KEYS];
230
231 // For multi-tap
232 private int mLastSentIndex;
233 private int mTapCount;
234 private long mLastTapTime;
235 private boolean mInMultiTap;
236 private static final int MULTITAP_INTERVAL = 800; // milliseconds
237 private StringBuilder mPreviewLabel = new StringBuilder(1);
238
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700239 /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
240 private boolean mDrawPending;
241 /** The dirty region in the keyboard bitmap */
242 private Rect mDirtyRect = new Rect();
243 /** The keyboard bitmap for faster updates */
244 private Bitmap mBuffer;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700245 /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
246 private boolean mKeyboardChanged;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700247 /** The canvas for the above mutable keyboard bitmap */
248 private Canvas mCanvas;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700249 /** The accessibility manager for accessibility support */
250 private AccessibilityManager mAccessibilityManager;
251 /** The audio manager for accessibility support */
252 private AudioManager mAudioManager;
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700253 /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
254 private boolean mHeadsetRequiredToHearPasswordsAnnounced;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700255
John Reckd0374c62015-10-20 13:25:01 -0700256 Handler mHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257
258 public KeyboardView(Context context, AttributeSet attrs) {
259 this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
260 }
261
Alan Viverette617feb92013-09-09 18:09:13 -0700262 public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
263 this(context, attrs, defStyleAttr, 0);
264 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265
Alan Viverette617feb92013-09-09 18:09:13 -0700266 public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
267 super(context, attrs, defStyleAttr, defStyleRes);
268
269 TypedArray a = context.obtainStyledAttributes(
270 attrs, android.R.styleable.KeyboardView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271
272 LayoutInflater inflate =
273 (LayoutInflater) context
274 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
275
276 int previewLayout = 0;
277 int keyTextSize = 0;
278
279 int n = a.getIndexCount();
John Reckd0374c62015-10-20 13:25:01 -0700280
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281 for (int i = 0; i < n; i++) {
282 int attr = a.getIndex(i);
283
284 switch (attr) {
285 case com.android.internal.R.styleable.KeyboardView_keyBackground:
286 mKeyBackground = a.getDrawable(attr);
287 break;
288 case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
289 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
290 break;
291 case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
292 previewLayout = a.getResourceId(attr, 0);
293 break;
294 case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
295 mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
296 break;
297 case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
298 mPreviewHeight = a.getDimensionPixelSize(attr, 80);
299 break;
300 case com.android.internal.R.styleable.KeyboardView_keyTextSize:
301 mKeyTextSize = a.getDimensionPixelSize(attr, 18);
302 break;
303 case com.android.internal.R.styleable.KeyboardView_keyTextColor:
304 mKeyTextColor = a.getColor(attr, 0xFF000000);
305 break;
306 case com.android.internal.R.styleable.KeyboardView_labelTextSize:
307 mLabelTextSize = a.getDimensionPixelSize(attr, 14);
308 break;
309 case com.android.internal.R.styleable.KeyboardView_popupLayout:
310 mPopupLayout = a.getResourceId(attr, 0);
311 break;
312 case com.android.internal.R.styleable.KeyboardView_shadowColor:
313 mShadowColor = a.getColor(attr, 0);
314 break;
315 case com.android.internal.R.styleable.KeyboardView_shadowRadius:
316 mShadowRadius = a.getFloat(attr, 0f);
317 break;
318 }
319 }
John Reckd0374c62015-10-20 13:25:01 -0700320
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 a = mContext.obtainStyledAttributes(
322 com.android.internal.R.styleable.Theme);
323 mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
324
325 mPreviewPopup = new PopupWindow(context);
326 if (previewLayout != 0) {
327 mPreviewText = (TextView) inflate.inflate(previewLayout, null);
328 mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
329 mPreviewPopup.setContentView(mPreviewText);
330 mPreviewPopup.setBackgroundDrawable(null);
331 } else {
332 mShowPreview = false;
333 }
John Reckd0374c62015-10-20 13:25:01 -0700334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 mPreviewPopup.setTouchable(false);
John Reckd0374c62015-10-20 13:25:01 -0700336
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 mPopupKeyboard = new PopupWindow(context);
338 mPopupKeyboard.setBackgroundDrawable(null);
339 //mPopupKeyboard.setClippingEnabled(false);
John Reckd0374c62015-10-20 13:25:01 -0700340
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 mPopupParent = this;
342 //mPredicting = true;
John Reckd0374c62015-10-20 13:25:01 -0700343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 mPaint = new Paint();
345 mPaint.setAntiAlias(true);
346 mPaint.setTextSize(keyTextSize);
347 mPaint.setTextAlign(Align.CENTER);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700348 mPaint.setAlpha(255);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349
350 mPadding = new Rect(0, 0, 0, 0);
351 mMiniKeyboardCache = new HashMap<Key,View>();
352 mKeyBackground.getPadding(mPadding);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700353
354 mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
355 mDisambiguateSwipe = getResources().getBoolean(
356 com.android.internal.R.bool.config_swipeDisambiguation);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700357
358 mAccessibilityManager = AccessibilityManager.getInstance(context);
359 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
360
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800361 resetMultiTap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 }
363
John Reckd0374c62015-10-20 13:25:01 -0700364 @Override
365 protected void onAttachedToWindow() {
366 super.onAttachedToWindow();
367 initGestureDetector();
368 if (mHandler == null) {
369 mHandler = new Handler() {
370 @Override
371 public void handleMessage(Message msg) {
372 switch (msg.what) {
373 case MSG_SHOW_PREVIEW:
374 showKey(msg.arg1);
375 break;
376 case MSG_REMOVE_PREVIEW:
377 mPreviewText.setVisibility(INVISIBLE);
378 break;
379 case MSG_REPEAT:
380 if (repeatKey()) {
381 Message repeat = Message.obtain(this, MSG_REPEAT);
382 sendMessageDelayed(repeat, REPEAT_INTERVAL);
383 }
384 break;
385 case MSG_LONGPRESS:
386 openPopupIfRequired((MotionEvent) msg.obj);
387 break;
388 }
389 }
390 };
391 }
392 }
Chet Haasea95e1082011-09-19 16:21:53 -0700393
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 private void initGestureDetector() {
John Reckd0374c62015-10-20 13:25:01 -0700395 if (mGestureDetector == null) {
396 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
397 @Override
398 public boolean onFling(MotionEvent me1, MotionEvent me2,
399 float velocityX, float velocityY) {
400 if (mPossiblePoly) return false;
401 final float absX = Math.abs(velocityX);
402 final float absY = Math.abs(velocityY);
403 float deltaX = me2.getX() - me1.getX();
404 float deltaY = me2.getY() - me1.getY();
405 int travelX = getWidth() / 2; // Half the keyboard width
406 int travelY = getHeight() / 2; // Half the keyboard height
407 mSwipeTracker.computeCurrentVelocity(1000);
408 final float endingVelocityX = mSwipeTracker.getXVelocity();
409 final float endingVelocityY = mSwipeTracker.getYVelocity();
410 boolean sendDownKey = false;
411 if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
412 if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
413 sendDownKey = true;
414 } else {
415 swipeRight();
416 return true;
417 }
418 } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
419 if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
420 sendDownKey = true;
421 } else {
422 swipeLeft();
423 return true;
424 }
425 } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
426 if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
427 sendDownKey = true;
428 } else {
429 swipeUp();
430 return true;
431 }
432 } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
433 if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
434 sendDownKey = true;
435 } else {
436 swipeDown();
437 return true;
438 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700439 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700440
John Reckd0374c62015-10-20 13:25:01 -0700441 if (sendDownKey) {
442 detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
443 }
444 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 }
John Reckd0374c62015-10-20 13:25:01 -0700446 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447
John Reckd0374c62015-10-20 13:25:01 -0700448 mGestureDetector.setIsLongpressEnabled(false);
449 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 }
451
452 public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
453 mKeyboardActionListener = listener;
454 }
455
456 /**
457 * Returns the {@link OnKeyboardActionListener} object.
458 * @return the listener attached to this keyboard
459 */
460 protected OnKeyboardActionListener getOnKeyboardActionListener() {
461 return mKeyboardActionListener;
462 }
463
464 /**
465 * Attaches a keyboard to this view. The keyboard can be switched at any time and the
466 * view will re-layout itself to accommodate the keyboard.
467 * @see Keyboard
468 * @see #getKeyboard()
469 * @param keyboard the keyboard to display in this view
470 */
471 public void setKeyboard(Keyboard keyboard) {
472 if (mKeyboard != null) {
473 showPreview(NOT_A_KEY);
474 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700475 // Remove any pending messages
476 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477 mKeyboard = keyboard;
478 List<Key> keys = mKeyboard.getKeys();
479 mKeys = keys.toArray(new Key[keys.size()]);
480 requestLayout();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700481 // Hint to reallocate the buffer if the size changed
482 mKeyboardChanged = true;
Amith Yamasanif04da952009-05-06 15:46:00 -0700483 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 computeProximityThreshold(keyboard);
485 mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700486 // Switching to a different keyboard should abort any pending keys so that the key up
487 // doesn't get delivered to the old or new keyboard
488 mAbortKey = true; // Until the next ACTION_DOWN
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 }
490
491 /**
492 * Returns the current keyboard being displayed by this view.
493 * @return the currently attached keyboard
494 * @see #setKeyboard(Keyboard)
495 */
496 public Keyboard getKeyboard() {
497 return mKeyboard;
498 }
John Reckd0374c62015-10-20 13:25:01 -0700499
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 /**
501 * Sets the state of the shift key of the keyboard, if any.
502 * @param shifted whether or not to enable the state of the shift key
503 * @return true if the shift key state changed, false if there was no change
504 * @see KeyboardView#isShifted()
505 */
506 public boolean setShifted(boolean shifted) {
507 if (mKeyboard != null) {
508 if (mKeyboard.setShifted(shifted)) {
509 // The whole keyboard probably needs to be redrawn
Amith Yamasanif04da952009-05-06 15:46:00 -0700510 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 return true;
512 }
513 }
514 return false;
515 }
516
517 /**
518 * Returns the state of the shift key of the keyboard, if any.
519 * @return true if the shift is in a pressed state, false otherwise. If there is
520 * no shift key on the keyboard or there is no keyboard attached, it returns false.
521 * @see KeyboardView#setShifted(boolean)
522 */
523 public boolean isShifted() {
524 if (mKeyboard != null) {
525 return mKeyboard.isShifted();
526 }
527 return false;
528 }
529
530 /**
531 * Enables or disables the key feedback popup. This is a popup that shows a magnified
John Reckd0374c62015-10-20 13:25:01 -0700532 * version of the depressed key. By default the preview is enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 * @param previewEnabled whether or not to enable the key feedback popup
534 * @see #isPreviewEnabled()
535 */
536 public void setPreviewEnabled(boolean previewEnabled) {
537 mShowPreview = previewEnabled;
538 }
539
540 /**
541 * Returns the enabled state of the key feedback popup.
542 * @return whether or not the key feedback popup is enabled
543 * @see #setPreviewEnabled(boolean)
544 */
545 public boolean isPreviewEnabled() {
546 return mShowPreview;
547 }
John Reckd0374c62015-10-20 13:25:01 -0700548
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 public void setVerticalCorrection(int verticalOffset) {
John Reckd0374c62015-10-20 13:25:01 -0700550
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 }
552 public void setPopupParent(View v) {
553 mPopupParent = v;
554 }
John Reckd0374c62015-10-20 13:25:01 -0700555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 public void setPopupOffset(int x, int y) {
557 mMiniKeyboardOffsetX = x;
558 mMiniKeyboardOffsetY = y;
559 if (mPreviewPopup.isShowing()) {
560 mPreviewPopup.dismiss();
561 }
562 }
563
564 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700565 * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
566 * codes for adjacent keys. When disabled, only the primary key code will be
567 * reported.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 * @param enabled whether or not the proximity correction is enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 */
570 public void setProximityCorrectionEnabled(boolean enabled) {
571 mProximityCorrectOn = enabled;
572 }
573
574 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700575 * Returns true if proximity correction is enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 */
577 public boolean isProximityCorrectionEnabled() {
578 return mProximityCorrectOn;
579 }
580
John Reckd0374c62015-10-20 13:25:01 -0700581 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 * Popup keyboard close button clicked.
John Reckd0374c62015-10-20 13:25:01 -0700583 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 */
585 public void onClick(View v) {
586 dismissPopupKeyboard();
587 }
588
589 private CharSequence adjustCase(CharSequence label) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800590 if (mKeyboard.isShifted() && label != null && label.length() < 3
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 && Character.isLowerCase(label.charAt(0))) {
592 label = label.toString().toUpperCase();
593 }
594 return label;
595 }
596
597 @Override
598 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
599 // Round up a little
600 if (mKeyboard == null) {
601 setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
602 } else {
603 int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
604 if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
605 width = MeasureSpec.getSize(widthMeasureSpec);
606 }
607 setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
608 }
609 }
610
611 /**
612 * Compute the average distance between adjacent keys (horizontally and vertically)
613 * and square it to get the proximity threshold. We use a square here and in computing
614 * the touch distance from a key's center to avoid taking a square root.
615 * @param keyboard
616 */
617 private void computeProximityThreshold(Keyboard keyboard) {
618 if (keyboard == null) return;
619 final Key[] keys = mKeys;
620 if (keys == null) return;
621 int length = keys.length;
622 int dimensionSum = 0;
623 for (int i = 0; i < length; i++) {
624 Key key = keys[i];
625 dimensionSum += Math.min(key.width, key.height) + key.gap;
626 }
627 if (dimensionSum < 0 || length == 0) return;
628 mProximityThreshold = (int) (dimensionSum * 1.4f / length);
629 mProximityThreshold *= mProximityThreshold; // Square it
630 }
631
632 @Override
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700633 public void onSizeChanged(int w, int h, int oldw, int oldh) {
634 super.onSizeChanged(w, h, oldw, oldh);
Chet Haasea95e1082011-09-19 16:21:53 -0700635 if (mKeyboard != null) {
636 mKeyboard.resize(w, h);
637 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700638 // Release the buffer, if any and it will be reallocated on the next draw
639 mBuffer = null;
640 }
641
642 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 public void onDraw(Canvas canvas) {
644 super.onDraw(canvas);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700645 if (mDrawPending || mBuffer == null || mKeyboardChanged) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700646 onBufferDraw();
647 }
648 canvas.drawBitmap(mBuffer, 0, 0, null);
649 }
Amith Yamasanie877ec62009-08-05 21:12:07 -0700650
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700651 private void onBufferDraw() {
Amith Yamasanie877ec62009-08-05 21:12:07 -0700652 if (mBuffer == null || mKeyboardChanged) {
653 if (mBuffer == null || mKeyboardChanged &&
654 (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
Romain Guy46a61bb2010-02-08 11:57:55 -0800655 // Make sure our bitmap is at least 1x1
656 final int width = Math.max(1, getWidth());
657 final int height = Math.max(1, getHeight());
658 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700659 mCanvas = new Canvas(mBuffer);
660 }
Amith Yamasanif04da952009-05-06 15:46:00 -0700661 invalidateAllKeys();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700662 mKeyboardChanged = false;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700663 }
664 final Canvas canvas = mCanvas;
665 canvas.clipRect(mDirtyRect, Op.REPLACE);
John Reckd0374c62015-10-20 13:25:01 -0700666
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667 if (mKeyboard == null) return;
John Reckd0374c62015-10-20 13:25:01 -0700668
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 final Paint paint = mPaint;
670 final Drawable keyBackground = mKeyBackground;
671 final Rect clipRegion = mClipRegion;
672 final Rect padding = mPadding;
673 final int kbdPaddingLeft = mPaddingLeft;
674 final int kbdPaddingTop = mPaddingTop;
675 final Key[] keys = mKeys;
676 final Key invalidKey = mInvalidatedKey;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700677
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800678 paint.setColor(mKeyTextColor);
679 boolean drawSingleKey = false;
680 if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700681 // Is clipRegion completely contained within the invalidated key?
682 if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
683 invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
684 invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
685 invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
686 drawSingleKey = true;
687 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700689 canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 final int keyCount = keys.length;
691 for (int i = 0; i < keyCount; i++) {
692 final Key key = keys[i];
693 if (drawSingleKey && invalidKey != key) {
694 continue;
695 }
696 int[] drawableState = key.getCurrentDrawableState();
697 keyBackground.setState(drawableState);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700698
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800699 // Switch the character to uppercase if shift is pressed
700 String label = key.label == null? null : adjustCase(key.label).toString();
John Reckd0374c62015-10-20 13:25:01 -0700701
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702 final Rect bounds = keyBackground.getBounds();
John Reckd0374c62015-10-20 13:25:01 -0700703 if (key.width != bounds.right ||
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 key.height != bounds.bottom) {
705 keyBackground.setBounds(0, 0, key.width, key.height);
706 }
707 canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
708 keyBackground.draw(canvas);
John Reckd0374c62015-10-20 13:25:01 -0700709
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 if (label != null) {
711 // For characters, use large font. For labels like "Done", use small font.
712 if (label.length() > 1 && key.codes.length < 2) {
713 paint.setTextSize(mLabelTextSize);
714 paint.setTypeface(Typeface.DEFAULT_BOLD);
715 } else {
716 paint.setTextSize(mKeyTextSize);
717 paint.setTypeface(Typeface.DEFAULT);
718 }
719 // Draw a drop shadow for the text
720 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
721 // Draw the text
722 canvas.drawText(label,
723 (key.width - padding.left - padding.right) / 2
724 + padding.left,
725 (key.height - padding.top - padding.bottom) / 2
726 + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
727 paint);
728 // Turn off drop shadow
729 paint.setShadowLayer(0, 0, 0, 0);
730 } else if (key.icon != null) {
John Reckd0374c62015-10-20 13:25:01 -0700731 final int drawableX = (key.width - padding.left - padding.right
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 - key.icon.getIntrinsicWidth()) / 2 + padding.left;
John Reckd0374c62015-10-20 13:25:01 -0700733 final int drawableY = (key.height - padding.top - padding.bottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800734 - key.icon.getIntrinsicHeight()) / 2 + padding.top;
735 canvas.translate(drawableX, drawableY);
John Reckd0374c62015-10-20 13:25:01 -0700736 key.icon.setBounds(0, 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
738 key.icon.draw(canvas);
739 canvas.translate(-drawableX, -drawableY);
740 }
741 canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
742 }
743 mInvalidatedKey = null;
744 // Overlay a dark rectangle to dim the keyboard
745 if (mMiniKeyboardOnScreen) {
746 paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
747 canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
748 }
749
750 if (DEBUG && mShowTouchPoints) {
751 paint.setAlpha(128);
752 paint.setColor(0xFFFF0000);
753 canvas.drawCircle(mStartX, mStartY, 3, paint);
754 canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
755 paint.setColor(0xFF0000FF);
756 canvas.drawCircle(mLastX, mLastY, 3, paint);
757 paint.setColor(0xFF00FF00);
758 canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
759 }
John Reckd0374c62015-10-20 13:25:01 -0700760
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700761 mDrawPending = false;
762 mDirtyRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763 }
764
765 private int getKeyIndices(int x, int y, int[] allKeys) {
766 final Key[] keys = mKeys;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 int primaryIndex = NOT_A_KEY;
768 int closestKey = NOT_A_KEY;
769 int closestKeyDist = mProximityThreshold + 1;
770 java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
771 int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
772 final int keyCount = nearestKeyIndices.length;
773 for (int i = 0; i < keyCount; i++) {
774 final Key key = keys[nearestKeyIndices[i]];
775 int dist = 0;
776 boolean isInside = key.isInside(x,y);
Amith Yamasaniec5df832010-02-08 15:24:57 -0800777 if (isInside) {
778 primaryIndex = nearestKeyIndices[i];
779 }
780
John Reckd0374c62015-10-20 13:25:01 -0700781 if (((mProximityCorrectOn
782 && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 || isInside)
784 && key.codes[0] > 32) {
785 // Find insertion point
786 final int nCodes = key.codes.length;
787 if (dist < closestKeyDist) {
788 closestKeyDist = dist;
789 closestKey = nearestKeyIndices[i];
790 }
John Reckd0374c62015-10-20 13:25:01 -0700791
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800792 if (allKeys == null) continue;
John Reckd0374c62015-10-20 13:25:01 -0700793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 for (int j = 0; j < mDistances.length; j++) {
795 if (mDistances[j] > dist) {
796 // Make space for nCodes codes
797 System.arraycopy(mDistances, j, mDistances, j + nCodes,
798 mDistances.length - j - nCodes);
799 System.arraycopy(allKeys, j, allKeys, j + nCodes,
800 allKeys.length - j - nCodes);
801 for (int c = 0; c < nCodes; c++) {
802 allKeys[j + c] = key.codes[c];
803 mDistances[j + c] = dist;
804 }
805 break;
806 }
807 }
808 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 }
810 if (primaryIndex == NOT_A_KEY) {
811 primaryIndex = closestKey;
812 }
813 return primaryIndex;
814 }
815
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700816 private void detectAndSendKey(int index, int x, int y, long eventTime) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800817 if (index != NOT_A_KEY && index < mKeys.length) {
818 final Key key = mKeys[index];
819 if (key.text != null) {
820 mKeyboardActionListener.onText(key.text);
821 mKeyboardActionListener.onRelease(NOT_A_KEY);
822 } else {
823 int code = key.codes[0];
824 //TextEntryState.keyPressedAt(key, x, y);
825 int[] codes = new int[MAX_NEARBY_KEYS];
826 Arrays.fill(codes, NOT_A_KEY);
827 getKeyIndices(x, y, codes);
828 // Multi-tap
829 if (mInMultiTap) {
830 if (mTapCount != -1) {
831 mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
832 } else {
833 mTapCount = 0;
834 }
835 code = key.codes[mTapCount];
836 }
837 mKeyboardActionListener.onKey(code, codes);
838 mKeyboardActionListener.onRelease(code);
839 }
840 mLastSentIndex = index;
841 mLastTapTime = eventTime;
842 }
843 }
844
845 /**
846 * Handle multi-tap keys by producing the key label for the current multi-tap state.
847 */
848 private CharSequence getPreviewText(Key key) {
849 if (mInMultiTap) {
850 // Multi-tap
851 mPreviewLabel.setLength(0);
852 mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
853 return adjustCase(mPreviewLabel);
854 } else {
855 return adjustCase(key.label);
856 }
857 }
John Reckd0374c62015-10-20 13:25:01 -0700858
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 private void showPreview(int keyIndex) {
860 int oldKeyIndex = mCurrentKeyIndex;
861 final PopupWindow previewPopup = mPreviewPopup;
John Reckd0374c62015-10-20 13:25:01 -0700862
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 mCurrentKeyIndex = keyIndex;
864 // Release the old key and press the new key
865 final Key[] keys = mKeys;
866 if (oldKeyIndex != mCurrentKeyIndex) {
867 if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700868 Key oldKey = keys[oldKeyIndex];
869 oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 invalidateKey(oldKeyIndex);
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700871 final int keyCode = oldKey.codes[0];
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700872 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700873 keyCode);
874 // TODO: We need to implement AccessibilityNodeProvider for this view.
875 sendAccessibilityEventForUnicodeCharacter(
876 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 }
878 if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700879 Key newKey = keys[mCurrentKeyIndex];
880 newKey.onPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881 invalidateKey(mCurrentKeyIndex);
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700882 final int keyCode = newKey.codes[0];
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700883 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700884 keyCode);
885 // TODO: We need to implement AccessibilityNodeProvider for this view.
886 sendAccessibilityEventForUnicodeCharacter(
887 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889 }
890 // If key changed and preview is on ...
891 if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
892 mHandler.removeMessages(MSG_SHOW_PREVIEW);
893 if (previewPopup.isShowing()) {
894 if (keyIndex == NOT_A_KEY) {
895 mHandler.sendMessageDelayed(mHandler
John Reckd0374c62015-10-20 13:25:01 -0700896 .obtainMessage(MSG_REMOVE_PREVIEW),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800897 DELAY_AFTER_PREVIEW);
898 }
899 }
900 if (keyIndex != NOT_A_KEY) {
901 if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
902 // Show right away, if it's already visible and finger is moving around
903 showKey(keyIndex);
904 } else {
905 mHandler.sendMessageDelayed(
John Reckd0374c62015-10-20 13:25:01 -0700906 mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 DELAY_BEFORE_PREVIEW);
908 }
909 }
910 }
911 }
John Reckd0374c62015-10-20 13:25:01 -0700912
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913 private void showKey(final int keyIndex) {
914 final PopupWindow previewPopup = mPreviewPopup;
915 final Key[] keys = mKeys;
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700916 if (keyIndex < 0 || keyIndex >= mKeys.length) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917 Key key = keys[keyIndex];
918 if (key.icon != null) {
John Reckd0374c62015-10-20 13:25:01 -0700919 mPreviewText.setCompoundDrawables(null, null, null,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800920 key.iconPreview != null ? key.iconPreview : key.icon);
921 mPreviewText.setText(null);
922 } else {
923 mPreviewText.setCompoundDrawables(null, null, null, null);
924 mPreviewText.setText(getPreviewText(key));
925 if (key.label.length() > 1 && key.codes.length < 2) {
Amith Yamasanie4037002009-07-23 17:38:15 -0700926 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
928 } else {
Amith Yamasanie4037002009-07-23 17:38:15 -0700929 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 mPreviewText.setTypeface(Typeface.DEFAULT);
931 }
932 }
John Reckd0374c62015-10-20 13:25:01 -0700933 mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
John Reckd0374c62015-10-20 13:25:01 -0700935 int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
937 final int popupHeight = mPreviewHeight;
938 LayoutParams lp = mPreviewText.getLayoutParams();
939 if (lp != null) {
940 lp.width = popupWidth;
941 lp.height = popupHeight;
942 }
943 if (!mPreviewCentered) {
944 mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
945 mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
946 } else {
947 // TODO: Fix this if centering is brought back
948 mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
949 mPopupPreviewY = - mPreviewText.getMeasuredHeight();
950 }
951 mHandler.removeMessages(MSG_REMOVE_PREVIEW);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900952 getLocationInWindow(mCoordinates);
953 mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
954 mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
955
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956 // Set the preview background state
957 mPreviewText.getBackground().setState(
958 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900959 mPopupPreviewX += mCoordinates[0];
960 mPopupPreviewY += mCoordinates[1];
Amith Yamasani42973a42010-03-19 17:21:44 -0700961
962 // If the popup cannot be shown above the key, put it on the side
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900963 getLocationOnScreen(mCoordinates);
964 if (mPopupPreviewY + mCoordinates[1] < 0) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700965 // If the key you're pressing is on the left side of the keyboard, show the popup on
966 // the right, offset by enough to see at least one key to the left/right.
967 if (key.x + key.width <= getWidth() / 2) {
968 mPopupPreviewX += (int) (key.width * 2.5);
969 } else {
970 mPopupPreviewX -= (int) (key.width * 2.5);
971 }
972 mPopupPreviewY += popupHeight;
973 }
974
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 if (previewPopup.isShowing()) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700976 previewPopup.update(mPopupPreviewX, mPopupPreviewY,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 popupWidth, popupHeight);
978 } else {
979 previewPopup.setWidth(popupWidth);
980 previewPopup.setHeight(popupHeight);
John Reckd0374c62015-10-20 13:25:01 -0700981 previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
Amith Yamasani42973a42010-03-19 17:21:44 -0700982 mPopupPreviewX, mPopupPreviewY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 }
984 mPreviewText.setVisibility(VISIBLE);
985 }
986
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700987 private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700988 if (mAccessibilityManager.isEnabled()) {
989 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
990 onInitializeAccessibilityEvent(event);
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700991 String text = null;
Svetoslav Ganov55f937a2011-12-05 11:42:07 -0800992 // This is very efficient since the properties are cached.
Alan Viveretteb6e0cb92014-11-24 15:13:43 -0800993 final boolean speakPassword = Settings.Secure.getIntForUser(
994 mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
Alan Viverette97524282014-12-02 16:24:24 -0800995 UserHandle.USER_CURRENT_OR_SELF) != 0;
Svetoslav Ganov55f937a2011-12-05 11:42:07 -0800996 // Add text only if password announcement is enabled or if headset is
997 // used to avoid leaking passwords.
998 if (speakPassword || mAudioManager.isBluetoothA2dpOn()
999 || mAudioManager.isWiredHeadsetOn()) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001000 switch (code) {
1001 case Keyboard.KEYCODE_ALT:
1002 text = mContext.getString(R.string.keyboardview_keycode_alt);
1003 break;
1004 case Keyboard.KEYCODE_CANCEL:
1005 text = mContext.getString(R.string.keyboardview_keycode_cancel);
1006 break;
1007 case Keyboard.KEYCODE_DELETE:
1008 text = mContext.getString(R.string.keyboardview_keycode_delete);
1009 break;
1010 case Keyboard.KEYCODE_DONE:
1011 text = mContext.getString(R.string.keyboardview_keycode_done);
1012 break;
1013 case Keyboard.KEYCODE_MODE_CHANGE:
1014 text = mContext.getString(R.string.keyboardview_keycode_mode_change);
1015 break;
1016 case Keyboard.KEYCODE_SHIFT:
1017 text = mContext.getString(R.string.keyboardview_keycode_shift);
1018 break;
1019 case '\n':
1020 text = mContext.getString(R.string.keyboardview_keycode_enter);
1021 break;
1022 default:
1023 text = String.valueOf((char) code);
1024 }
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001025 } else if (!mHeadsetRequiredToHearPasswordsAnnounced) {
1026 // We want the waring for required head set to be send with both the
1027 // hover enter and hover exit event, so set the flag after the exit.
1028 if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
1029 mHeadsetRequiredToHearPasswordsAnnounced = true;
1030 }
1031 text = mContext.getString(R.string.keyboard_headset_required_to_hear_password);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001032 } else {
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001033 text = mContext.getString(R.string.keyboard_password_character_no_headset);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001034 }
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001035 event.getText().add(text);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001036 mAccessibilityManager.sendAccessibilityEvent(event);
1037 }
1038 }
1039
Amith Yamasanif04da952009-05-06 15:46:00 -07001040 /**
1041 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
John Reckd0374c62015-10-20 13:25:01 -07001042 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
Amith Yamasanif04da952009-05-06 15:46:00 -07001043 * draws the cached buffer.
1044 * @see #invalidateKey(int)
1045 */
1046 public void invalidateAllKeys() {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001047 mDirtyRect.union(0, 0, getWidth(), getHeight());
1048 mDrawPending = true;
1049 invalidate();
1050 }
Amith Yamasanif04da952009-05-06 15:46:00 -07001051
1052 /**
1053 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1054 * one key is changing it's content. Any changes that affect the position or size of the key
1055 * may not be honored.
1056 * @param keyIndex the index of the key in the attached {@link Keyboard}.
1057 * @see #invalidateAllKeys
1058 */
1059 public void invalidateKey(int keyIndex) {
1060 if (mKeys == null) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061 if (keyIndex < 0 || keyIndex >= mKeys.length) {
1062 return;
1063 }
1064 final Key key = mKeys[keyIndex];
1065 mInvalidatedKey = key;
John Reckd0374c62015-10-20 13:25:01 -07001066 mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001067 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1068 onBufferDraw();
John Reckd0374c62015-10-20 13:25:01 -07001069 invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1071 }
1072
1073 private boolean openPopupIfRequired(MotionEvent me) {
1074 // Check if we have a popup layout specified first.
1075 if (mPopupLayout == 0) {
1076 return false;
1077 }
1078 if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
1079 return false;
1080 }
1081
John Reckd0374c62015-10-20 13:25:01 -07001082 Key popupKey = mKeys[mCurrentKey];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 boolean result = onLongPress(popupKey);
1084 if (result) {
1085 mAbortKey = true;
1086 showPreview(NOT_A_KEY);
1087 }
1088 return result;
1089 }
1090
1091 /**
1092 * Called when a key is long pressed. By default this will open any popup keyboard associated
1093 * with this key through the attributes popupLayout and popupCharacters.
1094 * @param popupKey the key that was long pressed
1095 * @return true if the long press is handled, false otherwise. Subclasses should call the
1096 * method on the base class if the subclass doesn't wish to handle the call.
1097 */
1098 protected boolean onLongPress(Key popupKey) {
1099 int popupKeyboardId = popupKey.popupResId;
1100
1101 if (popupKeyboardId != 0) {
1102 mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
1103 if (mMiniKeyboardContainer == null) {
1104 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
1105 Context.LAYOUT_INFLATER_SERVICE);
1106 mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
1107 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1108 com.android.internal.R.id.keyboardView);
1109 View closeButton = mMiniKeyboardContainer.findViewById(
Dianne Hackborn88fb1062009-03-27 14:59:22 -07001110 com.android.internal.R.id.closeButton);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111 if (closeButton != null) closeButton.setOnClickListener(this);
1112 mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
1113 public void onKey(int primaryCode, int[] keyCodes) {
1114 mKeyboardActionListener.onKey(primaryCode, keyCodes);
1115 dismissPopupKeyboard();
1116 }
John Reckd0374c62015-10-20 13:25:01 -07001117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 public void onText(CharSequence text) {
1119 mKeyboardActionListener.onText(text);
1120 dismissPopupKeyboard();
1121 }
John Reckd0374c62015-10-20 13:25:01 -07001122
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123 public void swipeLeft() { }
1124 public void swipeRight() { }
1125 public void swipeUp() { }
1126 public void swipeDown() { }
1127 public void onPress(int primaryCode) {
1128 mKeyboardActionListener.onPress(primaryCode);
1129 }
1130 public void onRelease(int primaryCode) {
1131 mKeyboardActionListener.onRelease(primaryCode);
1132 }
1133 });
1134 //mInputView.setSuggest(mSuggest);
1135 Keyboard keyboard;
1136 if (popupKey.popupCharacters != null) {
John Reckd0374c62015-10-20 13:25:01 -07001137 keyboard = new Keyboard(getContext(), popupKeyboardId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
1139 } else {
1140 keyboard = new Keyboard(getContext(), popupKeyboardId);
1141 }
1142 mMiniKeyboard.setKeyboard(keyboard);
1143 mMiniKeyboard.setPopupParent(this);
1144 mMiniKeyboardContainer.measure(
John Reckd0374c62015-10-20 13:25:01 -07001145 MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
John Reckd0374c62015-10-20 13:25:01 -07001147
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
1149 } else {
1150 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1151 com.android.internal.R.id.keyboardView);
1152 }
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001153 getLocationInWindow(mCoordinates);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 mPopupX = popupKey.x + mPaddingLeft;
1155 mPopupY = popupKey.y + mPaddingTop;
1156 mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
1157 mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001158 final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
1159 final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001160 mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
1161 mMiniKeyboard.setShifted(isShifted());
1162 mPopupKeyboard.setContentView(mMiniKeyboardContainer);
1163 mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
1164 mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
1165 mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
1166 mMiniKeyboardOnScreen = true;
1167 //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
Amith Yamasanif04da952009-05-06 15:46:00 -07001168 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 return true;
1170 }
1171 return false;
1172 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001173
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001174 @Override
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001175 public boolean onHoverEvent(MotionEvent event) {
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001176 if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001177 final int action = event.getAction();
1178 switch (action) {
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -07001179 case MotionEvent.ACTION_HOVER_ENTER: {
1180 event.setAction(MotionEvent.ACTION_DOWN);
1181 } break;
1182 case MotionEvent.ACTION_HOVER_MOVE: {
1183 event.setAction(MotionEvent.ACTION_MOVE);
1184 } break;
1185 case MotionEvent.ACTION_HOVER_EXIT: {
1186 event.setAction(MotionEvent.ACTION_UP);
1187 } break;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001188 }
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -07001189 return onTouchEvent(event);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001190 }
Svetoslav Ganov67578612011-11-01 16:29:45 -07001191 return true;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001192 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001193
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 @Override
Svetoslav Ganov67578612011-11-01 16:29:45 -07001195 public boolean onTouchEvent(MotionEvent me) {
John Reckd0374c62015-10-20 13:25:01 -07001196 // Convert multi-pointer up/down events to single up/down events to
Amith Yamasanie877ec62009-08-05 21:12:07 -07001197 // deal with the typical multi-pointer behavior of two-thumb typing
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001198 final int pointerCount = me.getPointerCount();
1199 final int action = me.getAction();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001200 boolean result = false;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001201 final long now = me.getEventTime();
1202
Amith Yamasanie877ec62009-08-05 21:12:07 -07001203 if (pointerCount != mOldPointerCount) {
Amith Yamasanie877ec62009-08-05 21:12:07 -07001204 if (pointerCount == 1) {
1205 // Send a down event for the latest pointer
1206 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
1207 me.getX(), me.getY(), me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001208 result = onModifiedTouchEvent(down, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001209 down.recycle();
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001210 // If it's an up action, then deliver the up as well.
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001211 if (action == MotionEvent.ACTION_UP) {
1212 result = onModifiedTouchEvent(me, true);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001213 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001214 } else {
1215 // Send an up event for the last pointer
1216 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
1217 mOldPointerX, mOldPointerY, me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001218 result = onModifiedTouchEvent(up, true);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001219 up.recycle();
1220 }
1221 } else {
1222 if (pointerCount == 1) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001223 result = onModifiedTouchEvent(me, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001224 mOldPointerX = me.getX();
1225 mOldPointerY = me.getY();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001226 } else {
1227 // Don't do anything when 2 pointers are down and moving.
1228 result = true;
1229 }
1230 }
1231 mOldPointerCount = pointerCount;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001232
Amith Yamasanie877ec62009-08-05 21:12:07 -07001233 return result;
1234 }
1235
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001236 private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
Amith Yamasani678cdbe2009-10-27 11:45:08 -07001237 int touchX = (int) me.getX() - mPaddingLeft;
Tadashi G. Takaoka0972d072010-07-30 15:18:56 -07001238 int touchY = (int) me.getY() - mPaddingTop;
1239 if (touchY >= -mVerticalCorrection)
1240 touchY += mVerticalCorrection;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001241 final int action = me.getAction();
1242 final long eventTime = me.getEventTime();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001243 int keyIndex = getKeyIndices(touchX, touchY, null);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001244 mPossiblePoly = possiblePoly;
1245
1246 // Track the last few movements to look for spurious swipes.
1247 if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
1248 mSwipeTracker.addMovement(me);
1249
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001250 // Ignore all motion events until a DOWN.
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001251 if (mAbortKey
1252 && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001253 return true;
1254 }
1255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001256 if (mGestureDetector.onTouchEvent(me)) {
1257 showPreview(NOT_A_KEY);
1258 mHandler.removeMessages(MSG_REPEAT);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001259 mHandler.removeMessages(MSG_LONGPRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 return true;
1261 }
John Reckd0374c62015-10-20 13:25:01 -07001262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 // Needs to be called after the gesture detector gets a turn, as it may have
1264 // displayed the mini keyboard
Amith Yamasani0743a382009-12-08 14:18:19 -08001265 if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001266 return true;
1267 }
1268
1269 switch (action) {
1270 case MotionEvent.ACTION_DOWN:
1271 mAbortKey = false;
1272 mStartX = touchX;
1273 mStartY = touchY;
1274 mLastCodeX = touchX;
1275 mLastCodeY = touchY;
1276 mLastKeyTime = 0;
1277 mCurrentKeyTime = 0;
1278 mLastKey = NOT_A_KEY;
1279 mCurrentKey = keyIndex;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001280 mDownKey = keyIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 mDownTime = me.getEventTime();
1282 mLastMoveTime = mDownTime;
1283 checkMultiTap(eventTime, keyIndex);
John Reckd0374c62015-10-20 13:25:01 -07001284 mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 mKeys[keyIndex].codes[0] : 0);
1286 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
1287 mRepeatKeyIndex = mCurrentKey;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 Message msg = mHandler.obtainMessage(MSG_REPEAT);
1289 mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001290 repeatKey();
1291 // Delivering the key could have caused an abort
1292 if (mAbortKey) {
1293 mRepeatKeyIndex = NOT_A_KEY;
1294 break;
1295 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001296 }
1297 if (mCurrentKey != NOT_A_KEY) {
1298 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1299 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1300 }
1301 showPreview(keyIndex);
1302 break;
1303
1304 case MotionEvent.ACTION_MOVE:
1305 boolean continueLongPress = false;
1306 if (keyIndex != NOT_A_KEY) {
1307 if (mCurrentKey == NOT_A_KEY) {
1308 mCurrentKey = keyIndex;
1309 mCurrentKeyTime = eventTime - mDownTime;
1310 } else {
1311 if (keyIndex == mCurrentKey) {
1312 mCurrentKeyTime += eventTime - mLastMoveTime;
1313 continueLongPress = true;
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001314 } else if (mRepeatKeyIndex == NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 resetMultiTap();
1316 mLastKey = mCurrentKey;
1317 mLastCodeX = mLastX;
1318 mLastCodeY = mLastY;
1319 mLastKeyTime =
1320 mCurrentKeyTime + eventTime - mLastMoveTime;
1321 mCurrentKey = keyIndex;
1322 mCurrentKeyTime = 0;
1323 }
1324 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325 }
1326 if (!continueLongPress) {
1327 // Cancel old longpress
1328 mHandler.removeMessages(MSG_LONGPRESS);
1329 // Start new longpress if key has changed
1330 if (keyIndex != NOT_A_KEY) {
1331 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1332 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1333 }
1334 }
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001335 showPreview(mCurrentKey);
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001336 mLastMoveTime = eventTime;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337 break;
1338
1339 case MotionEvent.ACTION_UP:
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001340 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341 if (keyIndex == mCurrentKey) {
1342 mCurrentKeyTime += eventTime - mLastMoveTime;
1343 } else {
1344 resetMultiTap();
1345 mLastKey = mCurrentKey;
1346 mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
1347 mCurrentKey = keyIndex;
1348 mCurrentKeyTime = 0;
1349 }
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001350 if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
1351 && mLastKey != NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 mCurrentKey = mLastKey;
1353 touchX = mLastCodeX;
1354 touchY = mLastCodeY;
1355 }
1356 showPreview(NOT_A_KEY);
1357 Arrays.fill(mKeyIndices, NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358 // If we're not on a repeating key (which sends on a DOWN event)
1359 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001360 detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001361 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001362 invalidateKey(keyIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001363 mRepeatKeyIndex = NOT_A_KEY;
1364 break;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001365 case MotionEvent.ACTION_CANCEL:
1366 removeMessages();
Amith Yamasani0743a382009-12-08 14:18:19 -08001367 dismissPopupKeyboard();
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001368 mAbortKey = true;
1369 showPreview(NOT_A_KEY);
1370 invalidateKey(mCurrentKey);
1371 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 }
1373 mLastX = touchX;
1374 mLastY = touchY;
1375 return true;
1376 }
1377
1378 private boolean repeatKey() {
1379 Key key = mKeys[mRepeatKeyIndex];
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001380 detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 return true;
1382 }
John Reckd0374c62015-10-20 13:25:01 -07001383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 protected void swipeRight() {
1385 mKeyboardActionListener.swipeRight();
1386 }
John Reckd0374c62015-10-20 13:25:01 -07001387
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001388 protected void swipeLeft() {
1389 mKeyboardActionListener.swipeLeft();
1390 }
1391
1392 protected void swipeUp() {
1393 mKeyboardActionListener.swipeUp();
1394 }
1395
1396 protected void swipeDown() {
1397 mKeyboardActionListener.swipeDown();
1398 }
1399
1400 public void closing() {
1401 if (mPreviewPopup.isShowing()) {
1402 mPreviewPopup.dismiss();
1403 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001404 removeMessages();
John Reckd0374c62015-10-20 13:25:01 -07001405
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001406 dismissPopupKeyboard();
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001407 mBuffer = null;
1408 mCanvas = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 mMiniKeyboardCache.clear();
1410 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001411
1412 private void removeMessages() {
Clara Bayarri5ba31aa2015-10-23 13:22:14 +01001413 if (mHandler != null) {
1414 mHandler.removeMessages(MSG_REPEAT);
1415 mHandler.removeMessages(MSG_LONGPRESS);
1416 mHandler.removeMessages(MSG_SHOW_PREVIEW);
1417 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001418 }
1419
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001420 @Override
1421 public void onDetachedFromWindow() {
1422 super.onDetachedFromWindow();
1423 closing();
1424 }
1425
1426 private void dismissPopupKeyboard() {
1427 if (mPopupKeyboard.isShowing()) {
1428 mPopupKeyboard.dismiss();
1429 mMiniKeyboardOnScreen = false;
Amith Yamasanif04da952009-05-06 15:46:00 -07001430 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001431 }
1432 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001433
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001434 public boolean handleBack() {
1435 if (mPopupKeyboard.isShowing()) {
1436 dismissPopupKeyboard();
1437 return true;
1438 }
1439 return false;
1440 }
1441
1442 private void resetMultiTap() {
1443 mLastSentIndex = NOT_A_KEY;
1444 mTapCount = 0;
1445 mLastTapTime = -1;
1446 mInMultiTap = false;
1447 }
John Reckd0374c62015-10-20 13:25:01 -07001448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 private void checkMultiTap(long eventTime, int keyIndex) {
1450 if (keyIndex == NOT_A_KEY) return;
1451 Key key = mKeys[keyIndex];
1452 if (key.codes.length > 1) {
1453 mInMultiTap = true;
1454 if (eventTime < mLastTapTime + MULTITAP_INTERVAL
1455 && keyIndex == mLastSentIndex) {
1456 mTapCount = (mTapCount + 1) % key.codes.length;
1457 return;
1458 } else {
1459 mTapCount = -1;
1460 return;
1461 }
1462 }
1463 if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
1464 resetMultiTap();
1465 }
1466 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001467
1468 private static class SwipeTracker {
1469
1470 static final int NUM_PAST = 4;
1471 static final int LONGEST_PAST_TIME = 200;
1472
1473 final float mPastX[] = new float[NUM_PAST];
1474 final float mPastY[] = new float[NUM_PAST];
1475 final long mPastTime[] = new long[NUM_PAST];
1476
1477 float mYVelocity;
1478 float mXVelocity;
1479
1480 public void clear() {
1481 mPastTime[0] = 0;
1482 }
1483
1484 public void addMovement(MotionEvent ev) {
1485 long time = ev.getEventTime();
1486 final int N = ev.getHistorySize();
1487 for (int i=0; i<N; i++) {
1488 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
1489 ev.getHistoricalEventTime(i));
1490 }
1491 addPoint(ev.getX(), ev.getY(), time);
1492 }
1493
1494 private void addPoint(float x, float y, long time) {
1495 int drop = -1;
1496 int i;
1497 final long[] pastTime = mPastTime;
1498 for (i=0; i<NUM_PAST; i++) {
1499 if (pastTime[i] == 0) {
1500 break;
1501 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
1502 drop = i;
1503 }
1504 }
1505 if (i == NUM_PAST && drop < 0) {
1506 drop = 0;
1507 }
1508 if (drop == i) drop--;
1509 final float[] pastX = mPastX;
1510 final float[] pastY = mPastY;
1511 if (drop >= 0) {
1512 final int start = drop+1;
1513 final int count = NUM_PAST-drop-1;
1514 System.arraycopy(pastX, start, pastX, 0, count);
1515 System.arraycopy(pastY, start, pastY, 0, count);
1516 System.arraycopy(pastTime, start, pastTime, 0, count);
1517 i -= (drop+1);
1518 }
1519 pastX[i] = x;
1520 pastY[i] = y;
1521 pastTime[i] = time;
1522 i++;
1523 if (i < NUM_PAST) {
1524 pastTime[i] = 0;
1525 }
1526 }
1527
1528 public void computeCurrentVelocity(int units) {
1529 computeCurrentVelocity(units, Float.MAX_VALUE);
1530 }
1531
1532 public void computeCurrentVelocity(int units, float maxVelocity) {
1533 final float[] pastX = mPastX;
1534 final float[] pastY = mPastY;
1535 final long[] pastTime = mPastTime;
1536
1537 final float oldestX = pastX[0];
1538 final float oldestY = pastY[0];
1539 final long oldestTime = pastTime[0];
1540 float accumX = 0;
1541 float accumY = 0;
1542 int N=0;
1543 while (N < NUM_PAST) {
1544 if (pastTime[N] == 0) {
1545 break;
1546 }
1547 N++;
1548 }
1549
1550 for (int i=1; i < N; i++) {
1551 final int dur = (int)(pastTime[i] - oldestTime);
1552 if (dur == 0) continue;
1553 float dist = pastX[i] - oldestX;
1554 float vel = (dist/dur) * units; // pixels/frame.
1555 if (accumX == 0) accumX = vel;
1556 else accumX = (accumX + vel) * .5f;
1557
1558 dist = pastY[i] - oldestY;
1559 vel = (dist/dur) * units; // pixels/frame.
1560 if (accumY == 0) accumY = vel;
1561 else accumY = (accumY + vel) * .5f;
1562 }
1563 mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
1564 : Math.min(accumX, maxVelocity);
1565 mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
1566 : Math.min(accumY, maxVelocity);
1567 }
1568
1569 public float getXVelocity() {
1570 return mXVelocity;
1571 }
1572
1573 public float getYVelocity() {
1574 return mYVelocity;
1575 }
1576 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001577}