blob: dfc70ef416af5cb1728c727605c7e2ecd9b51603 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008-2009 Google Inc.
3 *
4 * 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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, 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;
26import android.graphics.Typeface;
27import android.graphics.Paint.Align;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070028import android.graphics.Region.Op;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.graphics.drawable.Drawable;
30import android.inputmethodservice.Keyboard.Key;
31import android.os.Handler;
32import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.util.AttributeSet;
Amith Yamasanie4037002009-07-23 17:38:15 -070034import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.view.GestureDetector;
36import android.view.Gravity;
37import android.view.LayoutInflater;
38import android.view.MotionEvent;
39import android.view.View;
Amith Yamasanib974c7a2009-07-21 15:05:39 -070040import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.view.ViewGroup.LayoutParams;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.widget.PopupWindow;
43import android.widget.TextView;
44
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070045import com.android.internal.R;
46
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.util.Arrays;
48import java.util.HashMap;
49import java.util.List;
50import java.util.Map;
51
52/**
53 * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
54 * detecting key presses and touch movements.
55 *
56 * @attr ref android.R.styleable#KeyboardView_keyBackground
57 * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
58 * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
59 * @attr ref android.R.styleable#KeyboardView_labelTextSize
60 * @attr ref android.R.styleable#KeyboardView_keyTextSize
61 * @attr ref android.R.styleable#KeyboardView_keyTextColor
62 * @attr ref android.R.styleable#KeyboardView_verticalCorrection
63 * @attr ref android.R.styleable#KeyboardView_popupLayout
64 */
65public class KeyboardView extends View implements View.OnClickListener {
66
67 /**
68 * Listener for virtual keyboard events.
69 */
70 public interface OnKeyboardActionListener {
71
72 /**
73 * Called when the user presses a key. This is sent before the {@link #onKey} is called.
74 * For keys that repeat, this is only called once.
75 * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
76 * key, the value will be zero.
77 */
78 void onPress(int primaryCode);
79
80 /**
81 * Called when the user releases a key. This is sent after the {@link #onKey} is called.
82 * For keys that repeat, this is only called once.
83 * @param primaryCode the code of the key that was released
84 */
85 void onRelease(int primaryCode);
86
87 /**
88 * Send a key press to the listener.
89 * @param primaryCode this is the key that was pressed
90 * @param keyCodes the codes for all the possible alternative keys
91 * with the primary code being the first. If the primary key code is
92 * a single character such as an alphabet or number or symbol, the alternatives
93 * will include other characters that may be on the same key or adjacent keys.
94 * These codes are useful to correct for accidental presses of a key adjacent to
95 * the intended key.
96 */
97 void onKey(int primaryCode, int[] keyCodes);
98
99 /**
100 * Sends a sequence of characters to the listener.
101 * @param text the sequence of characters to be displayed.
102 */
103 void onText(CharSequence text);
104
105 /**
106 * Called when the user quickly moves the finger from right to left.
107 */
108 void swipeLeft();
109
110 /**
111 * Called when the user quickly moves the finger from left to right.
112 */
113 void swipeRight();
114
115 /**
116 * Called when the user quickly moves the finger from up to down.
117 */
118 void swipeDown();
119
120 /**
121 * Called when the user quickly moves the finger from down to up.
122 */
123 void swipeUp();
124 }
125
126 private static final boolean DEBUG = false;
127 private static final int NOT_A_KEY = -1;
128 private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
129 private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
130
131 private Keyboard mKeyboard;
132 private int mCurrentKeyIndex = NOT_A_KEY;
133 private int mLabelTextSize;
134 private int mKeyTextSize;
135 private int mKeyTextColor;
136 private float mShadowRadius;
137 private int mShadowColor;
138 private float mBackgroundDimAmount;
139
140 private TextView mPreviewText;
141 private PopupWindow mPreviewPopup;
142 private int mPreviewTextSizeLarge;
143 private int mPreviewOffset;
144 private int mPreviewHeight;
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900145 // Working variable
146 private final int[] mCoordinates = new int[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147
148 private PopupWindow mPopupKeyboard;
149 private View mMiniKeyboardContainer;
150 private KeyboardView mMiniKeyboard;
151 private boolean mMiniKeyboardOnScreen;
152 private View mPopupParent;
153 private int mMiniKeyboardOffsetX;
154 private int mMiniKeyboardOffsetY;
155 private Map<Key,View> mMiniKeyboardCache;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 private Key[] mKeys;
157
158 /** Listener for {@link OnKeyboardActionListener}. */
159 private OnKeyboardActionListener mKeyboardActionListener;
160
161 private static final int MSG_SHOW_PREVIEW = 1;
162 private static final int MSG_REMOVE_PREVIEW = 2;
163 private static final int MSG_REPEAT = 3;
164 private static final int MSG_LONGPRESS = 4;
Amith Yamasanie4037002009-07-23 17:38:15 -0700165
Amith Yamasani29d85df2009-08-11 16:42:51 -0700166 private static final int DELAY_BEFORE_PREVIEW = 0;
167 private static final int DELAY_AFTER_PREVIEW = 70;
Amith Yamasani7aa936e2010-02-11 18:51:25 -0800168 private static final int DEBOUNCE_TIME = 70;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169
170 private int mVerticalCorrection;
171 private int mProximityThreshold;
172
173 private boolean mPreviewCentered = false;
174 private boolean mShowPreview = true;
175 private boolean mShowTouchPoints = true;
176 private int mPopupPreviewX;
177 private int mPopupPreviewY;
Amith Yamasani42973a42010-03-19 17:21:44 -0700178 private int mWindowY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179
180 private int mLastX;
181 private int mLastY;
182 private int mStartX;
183 private int mStartY;
184
185 private boolean mProximityCorrectOn;
186
187 private Paint mPaint;
188 private Rect mPadding;
189
190 private long mDownTime;
191 private long mLastMoveTime;
192 private int mLastKey;
193 private int mLastCodeX;
194 private int mLastCodeY;
195 private int mCurrentKey = NOT_A_KEY;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700196 private int mDownKey = NOT_A_KEY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 private long mLastKeyTime;
198 private long mCurrentKeyTime;
199 private int[] mKeyIndices = new int[12];
200 private GestureDetector mGestureDetector;
201 private int mPopupX;
202 private int mPopupY;
203 private int mRepeatKeyIndex = NOT_A_KEY;
204 private int mPopupLayout;
205 private boolean mAbortKey;
206 private Key mInvalidatedKey;
207 private Rect mClipRegion = new Rect(0, 0, 0, 0);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700208 private boolean mPossiblePoly;
209 private SwipeTracker mSwipeTracker = new SwipeTracker();
210 private int mSwipeThreshold;
211 private boolean mDisambiguateSwipe;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700212
213 // Variables for dealing with multiple pointers
214 private int mOldPointerCount = 1;
215 private float mOldPointerX;
216 private float mOldPointerY;
217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 private Drawable mKeyBackground;
219
220 private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
221 private static final int REPEAT_START_DELAY = 400;
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700222 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223
224 private static int MAX_NEARBY_KEYS = 12;
225 private int[] mDistances = new int[MAX_NEARBY_KEYS];
226
227 // For multi-tap
228 private int mLastSentIndex;
229 private int mTapCount;
230 private long mLastTapTime;
231 private boolean mInMultiTap;
232 private static final int MULTITAP_INTERVAL = 800; // milliseconds
233 private StringBuilder mPreviewLabel = new StringBuilder(1);
234
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700235 /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
236 private boolean mDrawPending;
237 /** The dirty region in the keyboard bitmap */
238 private Rect mDirtyRect = new Rect();
239 /** The keyboard bitmap for faster updates */
240 private Bitmap mBuffer;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700241 /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
242 private boolean mKeyboardChanged;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700243 /** The canvas for the above mutable keyboard bitmap */
244 private Canvas mCanvas;
245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 Handler mHandler = new Handler() {
247 @Override
248 public void handleMessage(Message msg) {
249 switch (msg.what) {
250 case MSG_SHOW_PREVIEW:
251 showKey(msg.arg1);
252 break;
253 case MSG_REMOVE_PREVIEW:
254 mPreviewText.setVisibility(INVISIBLE);
255 break;
256 case MSG_REPEAT:
257 if (repeatKey()) {
258 Message repeat = Message.obtain(this, MSG_REPEAT);
259 sendMessageDelayed(repeat, REPEAT_INTERVAL);
260 }
261 break;
262 case MSG_LONGPRESS:
263 openPopupIfRequired((MotionEvent) msg.obj);
264 break;
265 }
266 }
267 };
268
269 public KeyboardView(Context context, AttributeSet attrs) {
270 this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
271 }
272
273 public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
274 super(context, attrs, defStyle);
275
276 TypedArray a =
277 context.obtainStyledAttributes(
278 attrs, android.R.styleable.KeyboardView, defStyle, 0);
279
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();
288
289 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 }
328
329 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 }
342
343 mPreviewPopup.setTouchable(false);
344
345 mPopupKeyboard = new PopupWindow(context);
346 mPopupKeyboard.setBackgroundDrawable(null);
347 //mPopupKeyboard.setClippingEnabled(false);
348
349 mPopupParent = this;
350 //mPredicting = true;
351
352 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 resetMultiTap();
366 initGestureDetector();
367 }
368
369 private void initGestureDetector() {
370 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
371 @Override
372 public boolean onFling(MotionEvent me1, MotionEvent me2,
373 float velocityX, float velocityY) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700374 if (mPossiblePoly) return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 final float absX = Math.abs(velocityX);
376 final float absY = Math.abs(velocityY);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700377 float deltaX = me2.getX() - me1.getX();
378 float deltaY = me2.getY() - me1.getY();
379 int travelX = getWidth() / 2; // Half the keyboard width
380 int travelY = getHeight() / 2; // Half the keyboard height
381 mSwipeTracker.computeCurrentVelocity(1000);
382 final float endingVelocityX = mSwipeTracker.getXVelocity();
383 final float endingVelocityY = mSwipeTracker.getYVelocity();
384 boolean sendDownKey = false;
385 if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
386 if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
387 sendDownKey = true;
388 } else {
389 swipeRight();
390 return true;
391 }
392 } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
393 if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
394 sendDownKey = true;
395 } else {
396 swipeLeft();
397 return true;
398 }
399 } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
400 if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
401 sendDownKey = true;
402 } else {
403 swipeUp();
404 return true;
405 }
406 } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
407 if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
408 sendDownKey = true;
409 } else {
410 swipeDown();
411 return true;
412 }
413 }
414
415 if (sendDownKey) {
416 detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 }
418 return false;
419 }
420 });
421
422 mGestureDetector.setIsLongpressEnabled(false);
423 }
424
425 public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
426 mKeyboardActionListener = listener;
427 }
428
429 /**
430 * Returns the {@link OnKeyboardActionListener} object.
431 * @return the listener attached to this keyboard
432 */
433 protected OnKeyboardActionListener getOnKeyboardActionListener() {
434 return mKeyboardActionListener;
435 }
436
437 /**
438 * Attaches a keyboard to this view. The keyboard can be switched at any time and the
439 * view will re-layout itself to accommodate the keyboard.
440 * @see Keyboard
441 * @see #getKeyboard()
442 * @param keyboard the keyboard to display in this view
443 */
444 public void setKeyboard(Keyboard keyboard) {
445 if (mKeyboard != null) {
446 showPreview(NOT_A_KEY);
447 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700448 // Remove any pending messages
449 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 mKeyboard = keyboard;
451 List<Key> keys = mKeyboard.getKeys();
452 mKeys = keys.toArray(new Key[keys.size()]);
453 requestLayout();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700454 // Hint to reallocate the buffer if the size changed
455 mKeyboardChanged = true;
Amith Yamasanif04da952009-05-06 15:46:00 -0700456 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 computeProximityThreshold(keyboard);
458 mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700459 // Switching to a different keyboard should abort any pending keys so that the key up
460 // doesn't get delivered to the old or new keyboard
461 mAbortKey = true; // Until the next ACTION_DOWN
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 }
463
464 /**
465 * Returns the current keyboard being displayed by this view.
466 * @return the currently attached keyboard
467 * @see #setKeyboard(Keyboard)
468 */
469 public Keyboard getKeyboard() {
470 return mKeyboard;
471 }
472
473 /**
474 * Sets the state of the shift key of the keyboard, if any.
475 * @param shifted whether or not to enable the state of the shift key
476 * @return true if the shift key state changed, false if there was no change
477 * @see KeyboardView#isShifted()
478 */
479 public boolean setShifted(boolean shifted) {
480 if (mKeyboard != null) {
481 if (mKeyboard.setShifted(shifted)) {
482 // The whole keyboard probably needs to be redrawn
Amith Yamasanif04da952009-05-06 15:46:00 -0700483 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 return true;
485 }
486 }
487 return false;
488 }
489
490 /**
491 * Returns the state of the shift key of the keyboard, if any.
492 * @return true if the shift is in a pressed state, false otherwise. If there is
493 * no shift key on the keyboard or there is no keyboard attached, it returns false.
494 * @see KeyboardView#setShifted(boolean)
495 */
496 public boolean isShifted() {
497 if (mKeyboard != null) {
498 return mKeyboard.isShifted();
499 }
500 return false;
501 }
502
503 /**
504 * Enables or disables the key feedback popup. This is a popup that shows a magnified
505 * version of the depressed key. By default the preview is enabled.
506 * @param previewEnabled whether or not to enable the key feedback popup
507 * @see #isPreviewEnabled()
508 */
509 public void setPreviewEnabled(boolean previewEnabled) {
510 mShowPreview = previewEnabled;
511 }
512
513 /**
514 * Returns the enabled state of the key feedback popup.
515 * @return whether or not the key feedback popup is enabled
516 * @see #setPreviewEnabled(boolean)
517 */
518 public boolean isPreviewEnabled() {
519 return mShowPreview;
520 }
521
522 public void setVerticalCorrection(int verticalOffset) {
523
524 }
525 public void setPopupParent(View v) {
526 mPopupParent = v;
527 }
528
529 public void setPopupOffset(int x, int y) {
530 mMiniKeyboardOffsetX = x;
531 mMiniKeyboardOffsetY = y;
532 if (mPreviewPopup.isShowing()) {
533 mPreviewPopup.dismiss();
534 }
535 }
536
537 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700538 * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
539 * codes for adjacent keys. When disabled, only the primary key code will be
540 * reported.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 * @param enabled whether or not the proximity correction is enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 */
543 public void setProximityCorrectionEnabled(boolean enabled) {
544 mProximityCorrectOn = enabled;
545 }
546
547 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700548 * Returns true if proximity correction is enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 */
550 public boolean isProximityCorrectionEnabled() {
551 return mProximityCorrectOn;
552 }
553
554 /**
555 * Popup keyboard close button clicked.
556 * @hide
557 */
558 public void onClick(View v) {
559 dismissPopupKeyboard();
560 }
561
562 private CharSequence adjustCase(CharSequence label) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800563 if (mKeyboard.isShifted() && label != null && label.length() < 3
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 && Character.isLowerCase(label.charAt(0))) {
565 label = label.toString().toUpperCase();
566 }
567 return label;
568 }
569
570 @Override
571 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
572 // Round up a little
573 if (mKeyboard == null) {
574 setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
575 } else {
576 int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
577 if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
578 width = MeasureSpec.getSize(widthMeasureSpec);
579 }
580 setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
581 }
582 }
583
584 /**
585 * Compute the average distance between adjacent keys (horizontally and vertically)
586 * and square it to get the proximity threshold. We use a square here and in computing
587 * the touch distance from a key's center to avoid taking a square root.
588 * @param keyboard
589 */
590 private void computeProximityThreshold(Keyboard keyboard) {
591 if (keyboard == null) return;
592 final Key[] keys = mKeys;
593 if (keys == null) return;
594 int length = keys.length;
595 int dimensionSum = 0;
596 for (int i = 0; i < length; i++) {
597 Key key = keys[i];
598 dimensionSum += Math.min(key.width, key.height) + key.gap;
599 }
600 if (dimensionSum < 0 || length == 0) return;
601 mProximityThreshold = (int) (dimensionSum * 1.4f / length);
602 mProximityThreshold *= mProximityThreshold; // Square it
603 }
604
605 @Override
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700606 public void onSizeChanged(int w, int h, int oldw, int oldh) {
607 super.onSizeChanged(w, h, oldw, oldh);
608 // Release the buffer, if any and it will be reallocated on the next draw
609 mBuffer = null;
610 }
611
612 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 public void onDraw(Canvas canvas) {
614 super.onDraw(canvas);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700615 if (mDrawPending || mBuffer == null || mKeyboardChanged) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700616 onBufferDraw();
617 }
618 canvas.drawBitmap(mBuffer, 0, 0, null);
619 }
Amith Yamasanie877ec62009-08-05 21:12:07 -0700620
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700621 private void onBufferDraw() {
Amith Yamasanie877ec62009-08-05 21:12:07 -0700622 if (mBuffer == null || mKeyboardChanged) {
623 if (mBuffer == null || mKeyboardChanged &&
624 (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
Romain Guy46a61bb2010-02-08 11:57:55 -0800625 // Make sure our bitmap is at least 1x1
626 final int width = Math.max(1, getWidth());
627 final int height = Math.max(1, getHeight());
628 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700629 mCanvas = new Canvas(mBuffer);
630 }
Amith Yamasanif04da952009-05-06 15:46:00 -0700631 invalidateAllKeys();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700632 mKeyboardChanged = false;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700633 }
634 final Canvas canvas = mCanvas;
635 canvas.clipRect(mDirtyRect, Op.REPLACE);
636
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 if (mKeyboard == null) return;
638
639 final Paint paint = mPaint;
640 final Drawable keyBackground = mKeyBackground;
641 final Rect clipRegion = mClipRegion;
642 final Rect padding = mPadding;
643 final int kbdPaddingLeft = mPaddingLeft;
644 final int kbdPaddingTop = mPaddingTop;
645 final Key[] keys = mKeys;
646 final Key invalidKey = mInvalidatedKey;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800648 paint.setColor(mKeyTextColor);
649 boolean drawSingleKey = false;
650 if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700651 // Is clipRegion completely contained within the invalidated key?
652 if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
653 invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
654 invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
655 invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
656 drawSingleKey = true;
657 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700659 canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800660 final int keyCount = keys.length;
661 for (int i = 0; i < keyCount; i++) {
662 final Key key = keys[i];
663 if (drawSingleKey && invalidKey != key) {
664 continue;
665 }
666 int[] drawableState = key.getCurrentDrawableState();
667 keyBackground.setState(drawableState);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700668
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 // Switch the character to uppercase if shift is pressed
670 String label = key.label == null? null : adjustCase(key.label).toString();
671
672 final Rect bounds = keyBackground.getBounds();
673 if (key.width != bounds.right ||
674 key.height != bounds.bottom) {
675 keyBackground.setBounds(0, 0, key.width, key.height);
676 }
677 canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
678 keyBackground.draw(canvas);
679
680 if (label != null) {
681 // For characters, use large font. For labels like "Done", use small font.
682 if (label.length() > 1 && key.codes.length < 2) {
683 paint.setTextSize(mLabelTextSize);
684 paint.setTypeface(Typeface.DEFAULT_BOLD);
685 } else {
686 paint.setTextSize(mKeyTextSize);
687 paint.setTypeface(Typeface.DEFAULT);
688 }
689 // Draw a drop shadow for the text
690 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
691 // Draw the text
692 canvas.drawText(label,
693 (key.width - padding.left - padding.right) / 2
694 + padding.left,
695 (key.height - padding.top - padding.bottom) / 2
696 + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
697 paint);
698 // Turn off drop shadow
699 paint.setShadowLayer(0, 0, 0, 0);
700 } else if (key.icon != null) {
701 final int drawableX = (key.width - padding.left - padding.right
702 - key.icon.getIntrinsicWidth()) / 2 + padding.left;
703 final int drawableY = (key.height - padding.top - padding.bottom
704 - key.icon.getIntrinsicHeight()) / 2 + padding.top;
705 canvas.translate(drawableX, drawableY);
706 key.icon.setBounds(0, 0,
707 key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
708 key.icon.draw(canvas);
709 canvas.translate(-drawableX, -drawableY);
710 }
711 canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
712 }
713 mInvalidatedKey = null;
714 // Overlay a dark rectangle to dim the keyboard
715 if (mMiniKeyboardOnScreen) {
716 paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
717 canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
718 }
719
720 if (DEBUG && mShowTouchPoints) {
721 paint.setAlpha(128);
722 paint.setColor(0xFFFF0000);
723 canvas.drawCircle(mStartX, mStartY, 3, paint);
724 canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
725 paint.setColor(0xFF0000FF);
726 canvas.drawCircle(mLastX, mLastY, 3, paint);
727 paint.setColor(0xFF00FF00);
728 canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
729 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700730
731 mDrawPending = false;
732 mDirtyRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 }
734
735 private int getKeyIndices(int x, int y, int[] allKeys) {
736 final Key[] keys = mKeys;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 int primaryIndex = NOT_A_KEY;
738 int closestKey = NOT_A_KEY;
739 int closestKeyDist = mProximityThreshold + 1;
740 java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
741 int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
742 final int keyCount = nearestKeyIndices.length;
743 for (int i = 0; i < keyCount; i++) {
744 final Key key = keys[nearestKeyIndices[i]];
745 int dist = 0;
746 boolean isInside = key.isInside(x,y);
Amith Yamasaniec5df832010-02-08 15:24:57 -0800747 if (isInside) {
748 primaryIndex = nearestKeyIndices[i];
749 }
750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 if (((mProximityCorrectOn
752 && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
753 || isInside)
754 && key.codes[0] > 32) {
755 // Find insertion point
756 final int nCodes = key.codes.length;
757 if (dist < closestKeyDist) {
758 closestKeyDist = dist;
759 closestKey = nearestKeyIndices[i];
760 }
761
762 if (allKeys == null) continue;
763
764 for (int j = 0; j < mDistances.length; j++) {
765 if (mDistances[j] > dist) {
766 // Make space for nCodes codes
767 System.arraycopy(mDistances, j, mDistances, j + nCodes,
768 mDistances.length - j - nCodes);
769 System.arraycopy(allKeys, j, allKeys, j + nCodes,
770 allKeys.length - j - nCodes);
771 for (int c = 0; c < nCodes; c++) {
772 allKeys[j + c] = key.codes[c];
773 mDistances[j + c] = dist;
774 }
775 break;
776 }
777 }
778 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800779 }
780 if (primaryIndex == NOT_A_KEY) {
781 primaryIndex = closestKey;
782 }
783 return primaryIndex;
784 }
785
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700786 private void detectAndSendKey(int index, int x, int y, long eventTime) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 if (index != NOT_A_KEY && index < mKeys.length) {
788 final Key key = mKeys[index];
789 if (key.text != null) {
790 mKeyboardActionListener.onText(key.text);
791 mKeyboardActionListener.onRelease(NOT_A_KEY);
792 } else {
793 int code = key.codes[0];
794 //TextEntryState.keyPressedAt(key, x, y);
795 int[] codes = new int[MAX_NEARBY_KEYS];
796 Arrays.fill(codes, NOT_A_KEY);
797 getKeyIndices(x, y, codes);
798 // Multi-tap
799 if (mInMultiTap) {
800 if (mTapCount != -1) {
801 mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
802 } else {
803 mTapCount = 0;
804 }
805 code = key.codes[mTapCount];
806 }
807 mKeyboardActionListener.onKey(code, codes);
808 mKeyboardActionListener.onRelease(code);
809 }
810 mLastSentIndex = index;
811 mLastTapTime = eventTime;
812 }
813 }
814
815 /**
816 * Handle multi-tap keys by producing the key label for the current multi-tap state.
817 */
818 private CharSequence getPreviewText(Key key) {
819 if (mInMultiTap) {
820 // Multi-tap
821 mPreviewLabel.setLength(0);
822 mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
823 return adjustCase(mPreviewLabel);
824 } else {
825 return adjustCase(key.label);
826 }
827 }
828
829 private void showPreview(int keyIndex) {
830 int oldKeyIndex = mCurrentKeyIndex;
831 final PopupWindow previewPopup = mPreviewPopup;
832
833 mCurrentKeyIndex = keyIndex;
834 // Release the old key and press the new key
835 final Key[] keys = mKeys;
836 if (oldKeyIndex != mCurrentKeyIndex) {
837 if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
838 keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
839 invalidateKey(oldKeyIndex);
840 }
841 if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
842 keys[mCurrentKeyIndex].onPressed();
843 invalidateKey(mCurrentKeyIndex);
844 }
845 }
846 // If key changed and preview is on ...
847 if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
848 mHandler.removeMessages(MSG_SHOW_PREVIEW);
849 if (previewPopup.isShowing()) {
850 if (keyIndex == NOT_A_KEY) {
851 mHandler.sendMessageDelayed(mHandler
852 .obtainMessage(MSG_REMOVE_PREVIEW),
853 DELAY_AFTER_PREVIEW);
854 }
855 }
856 if (keyIndex != NOT_A_KEY) {
857 if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
858 // Show right away, if it's already visible and finger is moving around
859 showKey(keyIndex);
860 } else {
861 mHandler.sendMessageDelayed(
862 mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
863 DELAY_BEFORE_PREVIEW);
864 }
865 }
866 }
867 }
868
869 private void showKey(final int keyIndex) {
870 final PopupWindow previewPopup = mPreviewPopup;
871 final Key[] keys = mKeys;
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700872 if (keyIndex < 0 || keyIndex >= mKeys.length) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 Key key = keys[keyIndex];
874 if (key.icon != null) {
875 mPreviewText.setCompoundDrawables(null, null, null,
876 key.iconPreview != null ? key.iconPreview : key.icon);
877 mPreviewText.setText(null);
878 } else {
879 mPreviewText.setCompoundDrawables(null, null, null, null);
880 mPreviewText.setText(getPreviewText(key));
881 if (key.label.length() > 1 && key.codes.length < 2) {
Amith Yamasanie4037002009-07-23 17:38:15 -0700882 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
884 } else {
Amith Yamasanie4037002009-07-23 17:38:15 -0700885 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886 mPreviewText.setTypeface(Typeface.DEFAULT);
887 }
888 }
889 mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
890 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
891 int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
892 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
893 final int popupHeight = mPreviewHeight;
894 LayoutParams lp = mPreviewText.getLayoutParams();
895 if (lp != null) {
896 lp.width = popupWidth;
897 lp.height = popupHeight;
898 }
899 if (!mPreviewCentered) {
900 mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
901 mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
902 } else {
903 // TODO: Fix this if centering is brought back
904 mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
905 mPopupPreviewY = - mPreviewText.getMeasuredHeight();
906 }
907 mHandler.removeMessages(MSG_REMOVE_PREVIEW);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900908 getLocationInWindow(mCoordinates);
909 mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
910 mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
911
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 // Set the preview background state
913 mPreviewText.getBackground().setState(
914 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900915 mPopupPreviewX += mCoordinates[0];
916 mPopupPreviewY += mCoordinates[1];
Amith Yamasani42973a42010-03-19 17:21:44 -0700917
918 // If the popup cannot be shown above the key, put it on the side
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900919 getLocationOnScreen(mCoordinates);
920 if (mPopupPreviewY + mCoordinates[1] < 0) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700921 // If the key you're pressing is on the left side of the keyboard, show the popup on
922 // the right, offset by enough to see at least one key to the left/right.
923 if (key.x + key.width <= getWidth() / 2) {
924 mPopupPreviewX += (int) (key.width * 2.5);
925 } else {
926 mPopupPreviewX -= (int) (key.width * 2.5);
927 }
928 mPopupPreviewY += popupHeight;
929 }
930
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 if (previewPopup.isShowing()) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700932 previewPopup.update(mPopupPreviewX, mPopupPreviewY,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 popupWidth, popupHeight);
934 } else {
935 previewPopup.setWidth(popupWidth);
936 previewPopup.setHeight(popupHeight);
937 previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
Amith Yamasani42973a42010-03-19 17:21:44 -0700938 mPopupPreviewX, mPopupPreviewY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 }
940 mPreviewText.setVisibility(VISIBLE);
941 }
942
Amith Yamasanif04da952009-05-06 15:46:00 -0700943 /**
944 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
945 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
946 * draws the cached buffer.
947 * @see #invalidateKey(int)
948 */
949 public void invalidateAllKeys() {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700950 mDirtyRect.union(0, 0, getWidth(), getHeight());
951 mDrawPending = true;
952 invalidate();
953 }
Amith Yamasanif04da952009-05-06 15:46:00 -0700954
955 /**
956 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
957 * one key is changing it's content. Any changes that affect the position or size of the key
958 * may not be honored.
959 * @param keyIndex the index of the key in the attached {@link Keyboard}.
960 * @see #invalidateAllKeys
961 */
962 public void invalidateKey(int keyIndex) {
963 if (mKeys == null) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 if (keyIndex < 0 || keyIndex >= mKeys.length) {
965 return;
966 }
967 final Key key = mKeys[keyIndex];
968 mInvalidatedKey = key;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700969 mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
970 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
971 onBufferDraw();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
973 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
974 }
975
976 private boolean openPopupIfRequired(MotionEvent me) {
977 // Check if we have a popup layout specified first.
978 if (mPopupLayout == 0) {
979 return false;
980 }
981 if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
982 return false;
983 }
984
985 Key popupKey = mKeys[mCurrentKey];
986 boolean result = onLongPress(popupKey);
987 if (result) {
988 mAbortKey = true;
989 showPreview(NOT_A_KEY);
990 }
991 return result;
992 }
993
994 /**
995 * Called when a key is long pressed. By default this will open any popup keyboard associated
996 * with this key through the attributes popupLayout and popupCharacters.
997 * @param popupKey the key that was long pressed
998 * @return true if the long press is handled, false otherwise. Subclasses should call the
999 * method on the base class if the subclass doesn't wish to handle the call.
1000 */
1001 protected boolean onLongPress(Key popupKey) {
1002 int popupKeyboardId = popupKey.popupResId;
1003
1004 if (popupKeyboardId != 0) {
1005 mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
1006 if (mMiniKeyboardContainer == null) {
1007 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
1008 Context.LAYOUT_INFLATER_SERVICE);
1009 mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
1010 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1011 com.android.internal.R.id.keyboardView);
1012 View closeButton = mMiniKeyboardContainer.findViewById(
Dianne Hackborn88fb1062009-03-27 14:59:22 -07001013 com.android.internal.R.id.closeButton);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 if (closeButton != null) closeButton.setOnClickListener(this);
1015 mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
1016 public void onKey(int primaryCode, int[] keyCodes) {
1017 mKeyboardActionListener.onKey(primaryCode, keyCodes);
1018 dismissPopupKeyboard();
1019 }
1020
1021 public void onText(CharSequence text) {
1022 mKeyboardActionListener.onText(text);
1023 dismissPopupKeyboard();
1024 }
1025
1026 public void swipeLeft() { }
1027 public void swipeRight() { }
1028 public void swipeUp() { }
1029 public void swipeDown() { }
1030 public void onPress(int primaryCode) {
1031 mKeyboardActionListener.onPress(primaryCode);
1032 }
1033 public void onRelease(int primaryCode) {
1034 mKeyboardActionListener.onRelease(primaryCode);
1035 }
1036 });
1037 //mInputView.setSuggest(mSuggest);
1038 Keyboard keyboard;
1039 if (popupKey.popupCharacters != null) {
1040 keyboard = new Keyboard(getContext(), popupKeyboardId,
1041 popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
1042 } else {
1043 keyboard = new Keyboard(getContext(), popupKeyboardId);
1044 }
1045 mMiniKeyboard.setKeyboard(keyboard);
1046 mMiniKeyboard.setPopupParent(this);
1047 mMiniKeyboardContainer.measure(
1048 MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
1049 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
1050
1051 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
1052 } else {
1053 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1054 com.android.internal.R.id.keyboardView);
1055 }
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001056 getLocationInWindow(mCoordinates);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 mPopupX = popupKey.x + mPaddingLeft;
1058 mPopupY = popupKey.y + mPaddingTop;
1059 mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
1060 mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001061 final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
1062 final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
1064 mMiniKeyboard.setShifted(isShifted());
1065 mPopupKeyboard.setContentView(mMiniKeyboardContainer);
1066 mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
1067 mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
1068 mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
1069 mMiniKeyboardOnScreen = true;
1070 //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
Amith Yamasanif04da952009-05-06 15:46:00 -07001071 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 return true;
1073 }
1074 return false;
1075 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001076
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001077 private long mOldEventTime;
1078 private boolean mUsedVelocity;
1079
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 @Override
1081 public boolean onTouchEvent(MotionEvent me) {
Amith Yamasanie877ec62009-08-05 21:12:07 -07001082 // Convert multi-pointer up/down events to single up/down events to
1083 // deal with the typical multi-pointer behavior of two-thumb typing
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001084 final int pointerCount = me.getPointerCount();
1085 final int action = me.getAction();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001086 boolean result = false;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001087 final long now = me.getEventTime();
1088
Amith Yamasanie877ec62009-08-05 21:12:07 -07001089 if (pointerCount != mOldPointerCount) {
Amith Yamasanie877ec62009-08-05 21:12:07 -07001090 if (pointerCount == 1) {
1091 // Send a down event for the latest pointer
1092 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
1093 me.getX(), me.getY(), me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001094 result = onModifiedTouchEvent(down, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001095 down.recycle();
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001096 // If it's an up action, then deliver the up as well.
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001097 if (action == MotionEvent.ACTION_UP) {
1098 result = onModifiedTouchEvent(me, true);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001099 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001100 } else {
1101 // Send an up event for the last pointer
1102 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
1103 mOldPointerX, mOldPointerY, me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001104 result = onModifiedTouchEvent(up, true);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001105 up.recycle();
1106 }
1107 } else {
1108 if (pointerCount == 1) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001109 result = onModifiedTouchEvent(me, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001110 mOldPointerX = me.getX();
1111 mOldPointerY = me.getY();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001112 } else {
1113 // Don't do anything when 2 pointers are down and moving.
1114 result = true;
1115 }
1116 }
1117 mOldPointerCount = pointerCount;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001118
Amith Yamasanie877ec62009-08-05 21:12:07 -07001119 return result;
1120 }
1121
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001122 private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
Amith Yamasani678cdbe2009-10-27 11:45:08 -07001123 int touchX = (int) me.getX() - mPaddingLeft;
Tadashi G. Takaoka0972d072010-07-30 15:18:56 -07001124 int touchY = (int) me.getY() - mPaddingTop;
1125 if (touchY >= -mVerticalCorrection)
1126 touchY += mVerticalCorrection;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001127 final int action = me.getAction();
1128 final long eventTime = me.getEventTime();
1129 mOldEventTime = eventTime;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001130 int keyIndex = getKeyIndices(touchX, touchY, null);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001131 mPossiblePoly = possiblePoly;
1132
1133 // Track the last few movements to look for spurious swipes.
1134 if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
1135 mSwipeTracker.addMovement(me);
1136
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001137 // Ignore all motion events until a DOWN.
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001138 if (mAbortKey
1139 && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001140 return true;
1141 }
1142
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 if (mGestureDetector.onTouchEvent(me)) {
1144 showPreview(NOT_A_KEY);
1145 mHandler.removeMessages(MSG_REPEAT);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001146 mHandler.removeMessages(MSG_LONGPRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147 return true;
1148 }
1149
1150 // Needs to be called after the gesture detector gets a turn, as it may have
1151 // displayed the mini keyboard
Amith Yamasani0743a382009-12-08 14:18:19 -08001152 if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153 return true;
1154 }
1155
1156 switch (action) {
1157 case MotionEvent.ACTION_DOWN:
1158 mAbortKey = false;
1159 mStartX = touchX;
1160 mStartY = touchY;
1161 mLastCodeX = touchX;
1162 mLastCodeY = touchY;
1163 mLastKeyTime = 0;
1164 mCurrentKeyTime = 0;
1165 mLastKey = NOT_A_KEY;
1166 mCurrentKey = keyIndex;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001167 mDownKey = keyIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 mDownTime = me.getEventTime();
1169 mLastMoveTime = mDownTime;
1170 checkMultiTap(eventTime, keyIndex);
1171 mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
1172 mKeys[keyIndex].codes[0] : 0);
1173 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
1174 mRepeatKeyIndex = mCurrentKey;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001175 Message msg = mHandler.obtainMessage(MSG_REPEAT);
1176 mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001177 repeatKey();
1178 // Delivering the key could have caused an abort
1179 if (mAbortKey) {
1180 mRepeatKeyIndex = NOT_A_KEY;
1181 break;
1182 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 }
1184 if (mCurrentKey != NOT_A_KEY) {
1185 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1186 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1187 }
1188 showPreview(keyIndex);
1189 break;
1190
1191 case MotionEvent.ACTION_MOVE:
1192 boolean continueLongPress = false;
1193 if (keyIndex != NOT_A_KEY) {
1194 if (mCurrentKey == NOT_A_KEY) {
1195 mCurrentKey = keyIndex;
1196 mCurrentKeyTime = eventTime - mDownTime;
1197 } else {
1198 if (keyIndex == mCurrentKey) {
1199 mCurrentKeyTime += eventTime - mLastMoveTime;
1200 continueLongPress = true;
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001201 } else if (mRepeatKeyIndex == NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202 resetMultiTap();
1203 mLastKey = mCurrentKey;
1204 mLastCodeX = mLastX;
1205 mLastCodeY = mLastY;
1206 mLastKeyTime =
1207 mCurrentKeyTime + eventTime - mLastMoveTime;
1208 mCurrentKey = keyIndex;
1209 mCurrentKeyTime = 0;
1210 }
1211 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 }
1213 if (!continueLongPress) {
1214 // Cancel old longpress
1215 mHandler.removeMessages(MSG_LONGPRESS);
1216 // Start new longpress if key has changed
1217 if (keyIndex != NOT_A_KEY) {
1218 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1219 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1220 }
1221 }
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001222 showPreview(mCurrentKey);
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001223 mLastMoveTime = eventTime;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 break;
1225
1226 case MotionEvent.ACTION_UP:
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001227 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 if (keyIndex == mCurrentKey) {
1229 mCurrentKeyTime += eventTime - mLastMoveTime;
1230 } else {
1231 resetMultiTap();
1232 mLastKey = mCurrentKey;
1233 mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
1234 mCurrentKey = keyIndex;
1235 mCurrentKeyTime = 0;
1236 }
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001237 if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
1238 && mLastKey != NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001239 mCurrentKey = mLastKey;
1240 touchX = mLastCodeX;
1241 touchY = mLastCodeY;
1242 }
1243 showPreview(NOT_A_KEY);
1244 Arrays.fill(mKeyIndices, NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245 // If we're not on a repeating key (which sends on a DOWN event)
1246 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001247 detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001249 invalidateKey(keyIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 mRepeatKeyIndex = NOT_A_KEY;
1251 break;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001252 case MotionEvent.ACTION_CANCEL:
1253 removeMessages();
Amith Yamasani0743a382009-12-08 14:18:19 -08001254 dismissPopupKeyboard();
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001255 mAbortKey = true;
1256 showPreview(NOT_A_KEY);
1257 invalidateKey(mCurrentKey);
1258 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001259 }
1260 mLastX = touchX;
1261 mLastY = touchY;
1262 return true;
1263 }
1264
1265 private boolean repeatKey() {
1266 Key key = mKeys[mRepeatKeyIndex];
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001267 detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268 return true;
1269 }
1270
1271 protected void swipeRight() {
1272 mKeyboardActionListener.swipeRight();
1273 }
1274
1275 protected void swipeLeft() {
1276 mKeyboardActionListener.swipeLeft();
1277 }
1278
1279 protected void swipeUp() {
1280 mKeyboardActionListener.swipeUp();
1281 }
1282
1283 protected void swipeDown() {
1284 mKeyboardActionListener.swipeDown();
1285 }
1286
1287 public void closing() {
1288 if (mPreviewPopup.isShowing()) {
1289 mPreviewPopup.dismiss();
1290 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001291 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001292
1293 dismissPopupKeyboard();
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001294 mBuffer = null;
1295 mCanvas = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001296 mMiniKeyboardCache.clear();
1297 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001298
1299 private void removeMessages() {
1300 mHandler.removeMessages(MSG_REPEAT);
1301 mHandler.removeMessages(MSG_LONGPRESS);
1302 mHandler.removeMessages(MSG_SHOW_PREVIEW);
1303 }
1304
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 @Override
1306 public void onDetachedFromWindow() {
1307 super.onDetachedFromWindow();
1308 closing();
1309 }
1310
1311 private void dismissPopupKeyboard() {
1312 if (mPopupKeyboard.isShowing()) {
1313 mPopupKeyboard.dismiss();
1314 mMiniKeyboardOnScreen = false;
Amith Yamasanif04da952009-05-06 15:46:00 -07001315 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001316 }
1317 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001318
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 public boolean handleBack() {
1320 if (mPopupKeyboard.isShowing()) {
1321 dismissPopupKeyboard();
1322 return true;
1323 }
1324 return false;
1325 }
1326
1327 private void resetMultiTap() {
1328 mLastSentIndex = NOT_A_KEY;
1329 mTapCount = 0;
1330 mLastTapTime = -1;
1331 mInMultiTap = false;
1332 }
1333
1334 private void checkMultiTap(long eventTime, int keyIndex) {
1335 if (keyIndex == NOT_A_KEY) return;
1336 Key key = mKeys[keyIndex];
1337 if (key.codes.length > 1) {
1338 mInMultiTap = true;
1339 if (eventTime < mLastTapTime + MULTITAP_INTERVAL
1340 && keyIndex == mLastSentIndex) {
1341 mTapCount = (mTapCount + 1) % key.codes.length;
1342 return;
1343 } else {
1344 mTapCount = -1;
1345 return;
1346 }
1347 }
1348 if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
1349 resetMultiTap();
1350 }
1351 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001352
1353 private static class SwipeTracker {
1354
1355 static final int NUM_PAST = 4;
1356 static final int LONGEST_PAST_TIME = 200;
1357
1358 final float mPastX[] = new float[NUM_PAST];
1359 final float mPastY[] = new float[NUM_PAST];
1360 final long mPastTime[] = new long[NUM_PAST];
1361
1362 float mYVelocity;
1363 float mXVelocity;
1364
1365 public void clear() {
1366 mPastTime[0] = 0;
1367 }
1368
1369 public void addMovement(MotionEvent ev) {
1370 long time = ev.getEventTime();
1371 final int N = ev.getHistorySize();
1372 for (int i=0; i<N; i++) {
1373 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
1374 ev.getHistoricalEventTime(i));
1375 }
1376 addPoint(ev.getX(), ev.getY(), time);
1377 }
1378
1379 private void addPoint(float x, float y, long time) {
1380 int drop = -1;
1381 int i;
1382 final long[] pastTime = mPastTime;
1383 for (i=0; i<NUM_PAST; i++) {
1384 if (pastTime[i] == 0) {
1385 break;
1386 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
1387 drop = i;
1388 }
1389 }
1390 if (i == NUM_PAST && drop < 0) {
1391 drop = 0;
1392 }
1393 if (drop == i) drop--;
1394 final float[] pastX = mPastX;
1395 final float[] pastY = mPastY;
1396 if (drop >= 0) {
1397 final int start = drop+1;
1398 final int count = NUM_PAST-drop-1;
1399 System.arraycopy(pastX, start, pastX, 0, count);
1400 System.arraycopy(pastY, start, pastY, 0, count);
1401 System.arraycopy(pastTime, start, pastTime, 0, count);
1402 i -= (drop+1);
1403 }
1404 pastX[i] = x;
1405 pastY[i] = y;
1406 pastTime[i] = time;
1407 i++;
1408 if (i < NUM_PAST) {
1409 pastTime[i] = 0;
1410 }
1411 }
1412
1413 public void computeCurrentVelocity(int units) {
1414 computeCurrentVelocity(units, Float.MAX_VALUE);
1415 }
1416
1417 public void computeCurrentVelocity(int units, float maxVelocity) {
1418 final float[] pastX = mPastX;
1419 final float[] pastY = mPastY;
1420 final long[] pastTime = mPastTime;
1421
1422 final float oldestX = pastX[0];
1423 final float oldestY = pastY[0];
1424 final long oldestTime = pastTime[0];
1425 float accumX = 0;
1426 float accumY = 0;
1427 int N=0;
1428 while (N < NUM_PAST) {
1429 if (pastTime[N] == 0) {
1430 break;
1431 }
1432 N++;
1433 }
1434
1435 for (int i=1; i < N; i++) {
1436 final int dur = (int)(pastTime[i] - oldestTime);
1437 if (dur == 0) continue;
1438 float dist = pastX[i] - oldestX;
1439 float vel = (dist/dur) * units; // pixels/frame.
1440 if (accumX == 0) accumX = vel;
1441 else accumX = (accumX + vel) * .5f;
1442
1443 dist = pastY[i] - oldestY;
1444 vel = (dist/dur) * units; // pixels/frame.
1445 if (accumY == 0) accumY = vel;
1446 else accumY = (accumY + vel) * .5f;
1447 }
1448 mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
1449 : Math.min(accumX, maxVelocity);
1450 mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
1451 : Math.min(accumY, maxVelocity);
1452 }
1453
1454 public float getXVelocity() {
1455 return mXVelocity;
1456 }
1457
1458 public float getYVelocity() {
1459 return mYVelocity;
1460 }
1461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462}