blob: 4916244098268468db7ca6fbabb228692299ad82 [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;
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;
Svetoslav Ganov55f937a2011-12-05 11:42:07 -080034import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.util.AttributeSet;
Amith Yamasanie4037002009-07-23 17:38:15 -070036import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.view.GestureDetector;
38import android.view.Gravity;
39import android.view.LayoutInflater;
40import android.view.MotionEvent;
41import android.view.View;
Amith Yamasanib974c7a2009-07-21 15:05:39 -070042import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.ViewGroup.LayoutParams;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -070044import android.view.accessibility.AccessibilityEvent;
45import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.widget.PopupWindow;
47import android.widget.TextView;
48
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070049import com.android.internal.R;
50
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import java.util.Arrays;
52import java.util.HashMap;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
58 * detecting key presses and touch movements.
59 *
60 * @attr ref android.R.styleable#KeyboardView_keyBackground
61 * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
62 * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
63 * @attr ref android.R.styleable#KeyboardView_labelTextSize
64 * @attr ref android.R.styleable#KeyboardView_keyTextSize
65 * @attr ref android.R.styleable#KeyboardView_keyTextColor
66 * @attr ref android.R.styleable#KeyboardView_verticalCorrection
67 * @attr ref android.R.styleable#KeyboardView_popupLayout
68 */
69public class KeyboardView extends View implements View.OnClickListener {
70
71 /**
72 * Listener for virtual keyboard events.
73 */
74 public interface OnKeyboardActionListener {
75
76 /**
77 * Called when the user presses a key. This is sent before the {@link #onKey} is called.
78 * For keys that repeat, this is only called once.
79 * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
80 * key, the value will be zero.
81 */
82 void onPress(int primaryCode);
83
84 /**
85 * Called when the user releases a key. This is sent after the {@link #onKey} is called.
86 * For keys that repeat, this is only called once.
87 * @param primaryCode the code of the key that was released
88 */
89 void onRelease(int primaryCode);
90
91 /**
92 * Send a key press to the listener.
93 * @param primaryCode this is the key that was pressed
94 * @param keyCodes the codes for all the possible alternative keys
95 * with the primary code being the first. If the primary key code is
96 * a single character such as an alphabet or number or symbol, the alternatives
97 * will include other characters that may be on the same key or adjacent keys.
98 * These codes are useful to correct for accidental presses of a key adjacent to
99 * the intended key.
100 */
101 void onKey(int primaryCode, int[] keyCodes);
102
103 /**
104 * Sends a sequence of characters to the listener.
105 * @param text the sequence of characters to be displayed.
106 */
107 void onText(CharSequence text);
108
109 /**
110 * Called when the user quickly moves the finger from right to left.
111 */
112 void swipeLeft();
113
114 /**
115 * Called when the user quickly moves the finger from left to right.
116 */
117 void swipeRight();
118
119 /**
120 * Called when the user quickly moves the finger from up to down.
121 */
122 void swipeDown();
123
124 /**
125 * Called when the user quickly moves the finger from down to up.
126 */
127 void swipeUp();
128 }
129
130 private static final boolean DEBUG = false;
131 private static final int NOT_A_KEY = -1;
132 private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
133 private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
134
135 private Keyboard mKeyboard;
136 private int mCurrentKeyIndex = NOT_A_KEY;
137 private int mLabelTextSize;
138 private int mKeyTextSize;
139 private int mKeyTextColor;
140 private float mShadowRadius;
141 private int mShadowColor;
142 private float mBackgroundDimAmount;
143
144 private TextView mPreviewText;
145 private PopupWindow mPreviewPopup;
146 private int mPreviewTextSizeLarge;
147 private int mPreviewOffset;
148 private int mPreviewHeight;
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900149 // Working variable
150 private final int[] mCoordinates = new int[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151
152 private PopupWindow mPopupKeyboard;
153 private View mMiniKeyboardContainer;
154 private KeyboardView mMiniKeyboard;
155 private boolean mMiniKeyboardOnScreen;
156 private View mPopupParent;
157 private int mMiniKeyboardOffsetX;
158 private int mMiniKeyboardOffsetY;
159 private Map<Key,View> mMiniKeyboardCache;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 private Key[] mKeys;
161
162 /** Listener for {@link OnKeyboardActionListener}. */
163 private OnKeyboardActionListener mKeyboardActionListener;
164
165 private static final int MSG_SHOW_PREVIEW = 1;
166 private static final int MSG_REMOVE_PREVIEW = 2;
167 private static final int MSG_REPEAT = 3;
168 private static final int MSG_LONGPRESS = 4;
Amith Yamasanie4037002009-07-23 17:38:15 -0700169
Amith Yamasani29d85df2009-08-11 16:42:51 -0700170 private static final int DELAY_BEFORE_PREVIEW = 0;
171 private static final int DELAY_AFTER_PREVIEW = 70;
Amith Yamasani7aa936e2010-02-11 18:51:25 -0800172 private static final int DEBOUNCE_TIME = 70;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173
174 private int mVerticalCorrection;
175 private int mProximityThreshold;
176
177 private boolean mPreviewCentered = false;
178 private boolean mShowPreview = true;
179 private boolean mShowTouchPoints = true;
180 private int mPopupPreviewX;
181 private int mPopupPreviewY;
182
183 private int mLastX;
184 private int mLastY;
185 private int mStartX;
186 private int mStartY;
187
188 private boolean mProximityCorrectOn;
189
190 private Paint mPaint;
191 private Rect mPadding;
192
193 private long mDownTime;
194 private long mLastMoveTime;
195 private int mLastKey;
196 private int mLastCodeX;
197 private int mLastCodeY;
198 private int mCurrentKey = NOT_A_KEY;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700199 private int mDownKey = NOT_A_KEY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 private long mLastKeyTime;
201 private long mCurrentKeyTime;
202 private int[] mKeyIndices = new int[12];
203 private GestureDetector mGestureDetector;
204 private int mPopupX;
205 private int mPopupY;
206 private int mRepeatKeyIndex = NOT_A_KEY;
207 private int mPopupLayout;
208 private boolean mAbortKey;
209 private Key mInvalidatedKey;
210 private Rect mClipRegion = new Rect(0, 0, 0, 0);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700211 private boolean mPossiblePoly;
212 private SwipeTracker mSwipeTracker = new SwipeTracker();
213 private int mSwipeThreshold;
214 private boolean mDisambiguateSwipe;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700215
216 // Variables for dealing with multiple pointers
217 private int mOldPointerCount = 1;
218 private float mOldPointerX;
219 private float mOldPointerY;
220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 private Drawable mKeyBackground;
222
223 private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
224 private static final int REPEAT_START_DELAY = 400;
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700225 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226
227 private static int MAX_NEARBY_KEYS = 12;
228 private int[] mDistances = new int[MAX_NEARBY_KEYS];
229
230 // For multi-tap
231 private int mLastSentIndex;
232 private int mTapCount;
233 private long mLastTapTime;
234 private boolean mInMultiTap;
235 private static final int MULTITAP_INTERVAL = 800; // milliseconds
236 private StringBuilder mPreviewLabel = new StringBuilder(1);
237
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700238 /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
239 private boolean mDrawPending;
240 /** The dirty region in the keyboard bitmap */
241 private Rect mDirtyRect = new Rect();
242 /** The keyboard bitmap for faster updates */
243 private Bitmap mBuffer;
Amith Yamasanie877ec62009-08-05 21:12:07 -0700244 /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
245 private boolean mKeyboardChanged;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700246 /** The canvas for the above mutable keyboard bitmap */
247 private Canvas mCanvas;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700248 /** The accessibility manager for accessibility support */
249 private AccessibilityManager mAccessibilityManager;
250 /** The audio manager for accessibility support */
251 private AudioManager mAudioManager;
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700252 /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
253 private boolean mHeadsetRequiredToHearPasswordsAnnounced;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700254
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 Handler mHandler = new Handler() {
256 @Override
257 public void handleMessage(Message msg) {
258 switch (msg.what) {
259 case MSG_SHOW_PREVIEW:
260 showKey(msg.arg1);
261 break;
262 case MSG_REMOVE_PREVIEW:
263 mPreviewText.setVisibility(INVISIBLE);
264 break;
265 case MSG_REPEAT:
266 if (repeatKey()) {
267 Message repeat = Message.obtain(this, MSG_REPEAT);
268 sendMessageDelayed(repeat, REPEAT_INTERVAL);
269 }
270 break;
271 case MSG_LONGPRESS:
272 openPopupIfRequired((MotionEvent) msg.obj);
273 break;
274 }
275 }
276 };
277
278 public KeyboardView(Context context, AttributeSet attrs) {
279 this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
280 }
281
282 public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
283 super(context, attrs, defStyle);
284
285 TypedArray a =
286 context.obtainStyledAttributes(
287 attrs, android.R.styleable.KeyboardView, defStyle, 0);
288
289 LayoutInflater inflate =
290 (LayoutInflater) context
291 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
292
293 int previewLayout = 0;
294 int keyTextSize = 0;
295
296 int n = a.getIndexCount();
297
298 for (int i = 0; i < n; i++) {
299 int attr = a.getIndex(i);
300
301 switch (attr) {
302 case com.android.internal.R.styleable.KeyboardView_keyBackground:
303 mKeyBackground = a.getDrawable(attr);
304 break;
305 case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
306 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
307 break;
308 case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
309 previewLayout = a.getResourceId(attr, 0);
310 break;
311 case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
312 mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
313 break;
314 case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
315 mPreviewHeight = a.getDimensionPixelSize(attr, 80);
316 break;
317 case com.android.internal.R.styleable.KeyboardView_keyTextSize:
318 mKeyTextSize = a.getDimensionPixelSize(attr, 18);
319 break;
320 case com.android.internal.R.styleable.KeyboardView_keyTextColor:
321 mKeyTextColor = a.getColor(attr, 0xFF000000);
322 break;
323 case com.android.internal.R.styleable.KeyboardView_labelTextSize:
324 mLabelTextSize = a.getDimensionPixelSize(attr, 14);
325 break;
326 case com.android.internal.R.styleable.KeyboardView_popupLayout:
327 mPopupLayout = a.getResourceId(attr, 0);
328 break;
329 case com.android.internal.R.styleable.KeyboardView_shadowColor:
330 mShadowColor = a.getColor(attr, 0);
331 break;
332 case com.android.internal.R.styleable.KeyboardView_shadowRadius:
333 mShadowRadius = a.getFloat(attr, 0f);
334 break;
335 }
336 }
337
338 a = mContext.obtainStyledAttributes(
339 com.android.internal.R.styleable.Theme);
340 mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
341
342 mPreviewPopup = new PopupWindow(context);
343 if (previewLayout != 0) {
344 mPreviewText = (TextView) inflate.inflate(previewLayout, null);
345 mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
346 mPreviewPopup.setContentView(mPreviewText);
347 mPreviewPopup.setBackgroundDrawable(null);
348 } else {
349 mShowPreview = false;
350 }
351
352 mPreviewPopup.setTouchable(false);
353
354 mPopupKeyboard = new PopupWindow(context);
355 mPopupKeyboard.setBackgroundDrawable(null);
356 //mPopupKeyboard.setClippingEnabled(false);
357
358 mPopupParent = this;
359 //mPredicting = true;
360
361 mPaint = new Paint();
362 mPaint.setAntiAlias(true);
363 mPaint.setTextSize(keyTextSize);
364 mPaint.setTextAlign(Align.CENTER);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700365 mPaint.setAlpha(255);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366
367 mPadding = new Rect(0, 0, 0, 0);
368 mMiniKeyboardCache = new HashMap<Key,View>();
369 mKeyBackground.getPadding(mPadding);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700370
371 mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
372 mDisambiguateSwipe = getResources().getBoolean(
373 com.android.internal.R.bool.config_swipeDisambiguation);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700374
375 mAccessibilityManager = AccessibilityManager.getInstance(context);
376 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
377
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 resetMultiTap();
379 initGestureDetector();
380 }
381
Chet Haasea95e1082011-09-19 16:21:53 -0700382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 private void initGestureDetector() {
384 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
385 @Override
386 public boolean onFling(MotionEvent me1, MotionEvent me2,
387 float velocityX, float velocityY) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700388 if (mPossiblePoly) return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 final float absX = Math.abs(velocityX);
390 final float absY = Math.abs(velocityY);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700391 float deltaX = me2.getX() - me1.getX();
392 float deltaY = me2.getY() - me1.getY();
393 int travelX = getWidth() / 2; // Half the keyboard width
394 int travelY = getHeight() / 2; // Half the keyboard height
395 mSwipeTracker.computeCurrentVelocity(1000);
396 final float endingVelocityX = mSwipeTracker.getXVelocity();
397 final float endingVelocityY = mSwipeTracker.getYVelocity();
398 boolean sendDownKey = false;
399 if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
400 if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
401 sendDownKey = true;
402 } else {
403 swipeRight();
404 return true;
405 }
406 } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
407 if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
408 sendDownKey = true;
409 } else {
410 swipeLeft();
411 return true;
412 }
413 } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
414 if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
415 sendDownKey = true;
416 } else {
417 swipeUp();
418 return true;
419 }
420 } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
421 if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
422 sendDownKey = true;
423 } else {
424 swipeDown();
425 return true;
426 }
427 }
428
429 if (sendDownKey) {
430 detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 }
432 return false;
433 }
434 });
435
436 mGestureDetector.setIsLongpressEnabled(false);
437 }
438
439 public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
440 mKeyboardActionListener = listener;
441 }
442
443 /**
444 * Returns the {@link OnKeyboardActionListener} object.
445 * @return the listener attached to this keyboard
446 */
447 protected OnKeyboardActionListener getOnKeyboardActionListener() {
448 return mKeyboardActionListener;
449 }
450
451 /**
452 * Attaches a keyboard to this view. The keyboard can be switched at any time and the
453 * view will re-layout itself to accommodate the keyboard.
454 * @see Keyboard
455 * @see #getKeyboard()
456 * @param keyboard the keyboard to display in this view
457 */
458 public void setKeyboard(Keyboard keyboard) {
459 if (mKeyboard != null) {
460 showPreview(NOT_A_KEY);
461 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700462 // Remove any pending messages
463 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 mKeyboard = keyboard;
465 List<Key> keys = mKeyboard.getKeys();
466 mKeys = keys.toArray(new Key[keys.size()]);
467 requestLayout();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700468 // Hint to reallocate the buffer if the size changed
469 mKeyboardChanged = true;
Amith Yamasanif04da952009-05-06 15:46:00 -0700470 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 computeProximityThreshold(keyboard);
472 mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
Amith Yamasanib974c7a2009-07-21 15:05:39 -0700473 // Switching to a different keyboard should abort any pending keys so that the key up
474 // doesn't get delivered to the old or new keyboard
475 mAbortKey = true; // Until the next ACTION_DOWN
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 }
477
478 /**
479 * Returns the current keyboard being displayed by this view.
480 * @return the currently attached keyboard
481 * @see #setKeyboard(Keyboard)
482 */
483 public Keyboard getKeyboard() {
484 return mKeyboard;
485 }
486
487 /**
488 * Sets the state of the shift key of the keyboard, if any.
489 * @param shifted whether or not to enable the state of the shift key
490 * @return true if the shift key state changed, false if there was no change
491 * @see KeyboardView#isShifted()
492 */
493 public boolean setShifted(boolean shifted) {
494 if (mKeyboard != null) {
495 if (mKeyboard.setShifted(shifted)) {
496 // The whole keyboard probably needs to be redrawn
Amith Yamasanif04da952009-05-06 15:46:00 -0700497 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 return true;
499 }
500 }
501 return false;
502 }
503
504 /**
505 * Returns the state of the shift key of the keyboard, if any.
506 * @return true if the shift is in a pressed state, false otherwise. If there is
507 * no shift key on the keyboard or there is no keyboard attached, it returns false.
508 * @see KeyboardView#setShifted(boolean)
509 */
510 public boolean isShifted() {
511 if (mKeyboard != null) {
512 return mKeyboard.isShifted();
513 }
514 return false;
515 }
516
517 /**
518 * Enables or disables the key feedback popup. This is a popup that shows a magnified
519 * version of the depressed key. By default the preview is enabled.
520 * @param previewEnabled whether or not to enable the key feedback popup
521 * @see #isPreviewEnabled()
522 */
523 public void setPreviewEnabled(boolean previewEnabled) {
524 mShowPreview = previewEnabled;
525 }
526
527 /**
528 * Returns the enabled state of the key feedback popup.
529 * @return whether or not the key feedback popup is enabled
530 * @see #setPreviewEnabled(boolean)
531 */
532 public boolean isPreviewEnabled() {
533 return mShowPreview;
534 }
535
536 public void setVerticalCorrection(int verticalOffset) {
537
538 }
539 public void setPopupParent(View v) {
540 mPopupParent = v;
541 }
542
543 public void setPopupOffset(int x, int y) {
544 mMiniKeyboardOffsetX = x;
545 mMiniKeyboardOffsetY = y;
546 if (mPreviewPopup.isShowing()) {
547 mPreviewPopup.dismiss();
548 }
549 }
550
551 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700552 * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
553 * codes for adjacent keys. When disabled, only the primary key code will be
554 * reported.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 * @param enabled whether or not the proximity correction is enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 */
557 public void setProximityCorrectionEnabled(boolean enabled) {
558 mProximityCorrectOn = enabled;
559 }
560
561 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700562 * Returns true if proximity correction is enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 */
564 public boolean isProximityCorrectionEnabled() {
565 return mProximityCorrectOn;
566 }
567
568 /**
569 * Popup keyboard close button clicked.
570 * @hide
571 */
572 public void onClick(View v) {
573 dismissPopupKeyboard();
574 }
575
576 private CharSequence adjustCase(CharSequence label) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800577 if (mKeyboard.isShifted() && label != null && label.length() < 3
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 && Character.isLowerCase(label.charAt(0))) {
579 label = label.toString().toUpperCase();
580 }
581 return label;
582 }
583
584 @Override
585 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
586 // Round up a little
587 if (mKeyboard == null) {
588 setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
589 } else {
590 int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
591 if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
592 width = MeasureSpec.getSize(widthMeasureSpec);
593 }
594 setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
595 }
596 }
597
598 /**
599 * Compute the average distance between adjacent keys (horizontally and vertically)
600 * and square it to get the proximity threshold. We use a square here and in computing
601 * the touch distance from a key's center to avoid taking a square root.
602 * @param keyboard
603 */
604 private void computeProximityThreshold(Keyboard keyboard) {
605 if (keyboard == null) return;
606 final Key[] keys = mKeys;
607 if (keys == null) return;
608 int length = keys.length;
609 int dimensionSum = 0;
610 for (int i = 0; i < length; i++) {
611 Key key = keys[i];
612 dimensionSum += Math.min(key.width, key.height) + key.gap;
613 }
614 if (dimensionSum < 0 || length == 0) return;
615 mProximityThreshold = (int) (dimensionSum * 1.4f / length);
616 mProximityThreshold *= mProximityThreshold; // Square it
617 }
618
619 @Override
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700620 public void onSizeChanged(int w, int h, int oldw, int oldh) {
621 super.onSizeChanged(w, h, oldw, oldh);
Chet Haasea95e1082011-09-19 16:21:53 -0700622 if (mKeyboard != null) {
623 mKeyboard.resize(w, h);
624 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700625 // Release the buffer, if any and it will be reallocated on the next draw
626 mBuffer = null;
627 }
628
629 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 public void onDraw(Canvas canvas) {
631 super.onDraw(canvas);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700632 if (mDrawPending || mBuffer == null || mKeyboardChanged) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700633 onBufferDraw();
634 }
635 canvas.drawBitmap(mBuffer, 0, 0, null);
636 }
Amith Yamasanie877ec62009-08-05 21:12:07 -0700637
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700638 private void onBufferDraw() {
Amith Yamasanie877ec62009-08-05 21:12:07 -0700639 if (mBuffer == null || mKeyboardChanged) {
640 if (mBuffer == null || mKeyboardChanged &&
641 (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
Romain Guy46a61bb2010-02-08 11:57:55 -0800642 // Make sure our bitmap is at least 1x1
643 final int width = Math.max(1, getWidth());
644 final int height = Math.max(1, getHeight());
645 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700646 mCanvas = new Canvas(mBuffer);
647 }
Amith Yamasanif04da952009-05-06 15:46:00 -0700648 invalidateAllKeys();
Amith Yamasanie877ec62009-08-05 21:12:07 -0700649 mKeyboardChanged = false;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700650 }
651 final Canvas canvas = mCanvas;
652 canvas.clipRect(mDirtyRect, Op.REPLACE);
653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 if (mKeyboard == null) return;
655
656 final Paint paint = mPaint;
657 final Drawable keyBackground = mKeyBackground;
658 final Rect clipRegion = mClipRegion;
659 final Rect padding = mPadding;
660 final int kbdPaddingLeft = mPaddingLeft;
661 final int kbdPaddingTop = mPaddingTop;
662 final Key[] keys = mKeys;
663 final Key invalidKey = mInvalidatedKey;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700664
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 paint.setColor(mKeyTextColor);
666 boolean drawSingleKey = false;
667 if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700668 // Is clipRegion completely contained within the invalidated key?
669 if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
670 invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
671 invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
672 invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
673 drawSingleKey = true;
674 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700676 canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 final int keyCount = keys.length;
678 for (int i = 0; i < keyCount; i++) {
679 final Key key = keys[i];
680 if (drawSingleKey && invalidKey != key) {
681 continue;
682 }
683 int[] drawableState = key.getCurrentDrawableState();
684 keyBackground.setState(drawableState);
Amith Yamasanie877ec62009-08-05 21:12:07 -0700685
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 // Switch the character to uppercase if shift is pressed
687 String label = key.label == null? null : adjustCase(key.label).toString();
688
689 final Rect bounds = keyBackground.getBounds();
690 if (key.width != bounds.right ||
691 key.height != bounds.bottom) {
692 keyBackground.setBounds(0, 0, key.width, key.height);
693 }
694 canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
695 keyBackground.draw(canvas);
696
697 if (label != null) {
698 // For characters, use large font. For labels like "Done", use small font.
699 if (label.length() > 1 && key.codes.length < 2) {
700 paint.setTextSize(mLabelTextSize);
701 paint.setTypeface(Typeface.DEFAULT_BOLD);
702 } else {
703 paint.setTextSize(mKeyTextSize);
704 paint.setTypeface(Typeface.DEFAULT);
705 }
706 // Draw a drop shadow for the text
707 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
708 // Draw the text
709 canvas.drawText(label,
710 (key.width - padding.left - padding.right) / 2
711 + padding.left,
712 (key.height - padding.top - padding.bottom) / 2
713 + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
714 paint);
715 // Turn off drop shadow
716 paint.setShadowLayer(0, 0, 0, 0);
717 } else if (key.icon != null) {
718 final int drawableX = (key.width - padding.left - padding.right
719 - key.icon.getIntrinsicWidth()) / 2 + padding.left;
720 final int drawableY = (key.height - padding.top - padding.bottom
721 - key.icon.getIntrinsicHeight()) / 2 + padding.top;
722 canvas.translate(drawableX, drawableY);
723 key.icon.setBounds(0, 0,
724 key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
725 key.icon.draw(canvas);
726 canvas.translate(-drawableX, -drawableY);
727 }
728 canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
729 }
730 mInvalidatedKey = null;
731 // Overlay a dark rectangle to dim the keyboard
732 if (mMiniKeyboardOnScreen) {
733 paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
734 canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
735 }
736
737 if (DEBUG && mShowTouchPoints) {
738 paint.setAlpha(128);
739 paint.setColor(0xFFFF0000);
740 canvas.drawCircle(mStartX, mStartY, 3, paint);
741 canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
742 paint.setColor(0xFF0000FF);
743 canvas.drawCircle(mLastX, mLastY, 3, paint);
744 paint.setColor(0xFF00FF00);
745 canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
746 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700747
748 mDrawPending = false;
749 mDirtyRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 }
751
752 private int getKeyIndices(int x, int y, int[] allKeys) {
753 final Key[] keys = mKeys;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 int primaryIndex = NOT_A_KEY;
755 int closestKey = NOT_A_KEY;
756 int closestKeyDist = mProximityThreshold + 1;
757 java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
758 int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
759 final int keyCount = nearestKeyIndices.length;
760 for (int i = 0; i < keyCount; i++) {
761 final Key key = keys[nearestKeyIndices[i]];
762 int dist = 0;
763 boolean isInside = key.isInside(x,y);
Amith Yamasaniec5df832010-02-08 15:24:57 -0800764 if (isInside) {
765 primaryIndex = nearestKeyIndices[i];
766 }
767
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800768 if (((mProximityCorrectOn
769 && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
770 || isInside)
771 && key.codes[0] > 32) {
772 // Find insertion point
773 final int nCodes = key.codes.length;
774 if (dist < closestKeyDist) {
775 closestKeyDist = dist;
776 closestKey = nearestKeyIndices[i];
777 }
778
779 if (allKeys == null) continue;
780
781 for (int j = 0; j < mDistances.length; j++) {
782 if (mDistances[j] > dist) {
783 // Make space for nCodes codes
784 System.arraycopy(mDistances, j, mDistances, j + nCodes,
785 mDistances.length - j - nCodes);
786 System.arraycopy(allKeys, j, allKeys, j + nCodes,
787 allKeys.length - j - nCodes);
788 for (int c = 0; c < nCodes; c++) {
789 allKeys[j + c] = key.codes[c];
790 mDistances[j + c] = dist;
791 }
792 break;
793 }
794 }
795 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 }
797 if (primaryIndex == NOT_A_KEY) {
798 primaryIndex = closestKey;
799 }
800 return primaryIndex;
801 }
802
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -0700803 private void detectAndSendKey(int index, int x, int y, long eventTime) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 if (index != NOT_A_KEY && index < mKeys.length) {
805 final Key key = mKeys[index];
806 if (key.text != null) {
807 mKeyboardActionListener.onText(key.text);
808 mKeyboardActionListener.onRelease(NOT_A_KEY);
809 } else {
810 int code = key.codes[0];
811 //TextEntryState.keyPressedAt(key, x, y);
812 int[] codes = new int[MAX_NEARBY_KEYS];
813 Arrays.fill(codes, NOT_A_KEY);
814 getKeyIndices(x, y, codes);
815 // Multi-tap
816 if (mInMultiTap) {
817 if (mTapCount != -1) {
818 mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
819 } else {
820 mTapCount = 0;
821 }
822 code = key.codes[mTapCount];
823 }
824 mKeyboardActionListener.onKey(code, codes);
825 mKeyboardActionListener.onRelease(code);
826 }
827 mLastSentIndex = index;
828 mLastTapTime = eventTime;
829 }
830 }
831
832 /**
833 * Handle multi-tap keys by producing the key label for the current multi-tap state.
834 */
835 private CharSequence getPreviewText(Key key) {
836 if (mInMultiTap) {
837 // Multi-tap
838 mPreviewLabel.setLength(0);
839 mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
840 return adjustCase(mPreviewLabel);
841 } else {
842 return adjustCase(key.label);
843 }
844 }
845
846 private void showPreview(int keyIndex) {
847 int oldKeyIndex = mCurrentKeyIndex;
848 final PopupWindow previewPopup = mPreviewPopup;
849
850 mCurrentKeyIndex = keyIndex;
851 // Release the old key and press the new key
852 final Key[] keys = mKeys;
853 if (oldKeyIndex != mCurrentKeyIndex) {
854 if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700855 Key oldKey = keys[oldKeyIndex];
856 oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 invalidateKey(oldKeyIndex);
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700858 final int keyCode = oldKey.codes[0];
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700859 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700860 keyCode);
861 // TODO: We need to implement AccessibilityNodeProvider for this view.
862 sendAccessibilityEventForUnicodeCharacter(
863 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 }
865 if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700866 Key newKey = keys[mCurrentKeyIndex];
867 newKey.onPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 invalidateKey(mCurrentKeyIndex);
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700869 final int keyCode = newKey.codes[0];
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700870 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -0700871 keyCode);
872 // TODO: We need to implement AccessibilityNodeProvider for this view.
873 sendAccessibilityEventForUnicodeCharacter(
874 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 }
876 }
877 // If key changed and preview is on ...
878 if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
879 mHandler.removeMessages(MSG_SHOW_PREVIEW);
880 if (previewPopup.isShowing()) {
881 if (keyIndex == NOT_A_KEY) {
882 mHandler.sendMessageDelayed(mHandler
883 .obtainMessage(MSG_REMOVE_PREVIEW),
884 DELAY_AFTER_PREVIEW);
885 }
886 }
887 if (keyIndex != NOT_A_KEY) {
888 if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
889 // Show right away, if it's already visible and finger is moving around
890 showKey(keyIndex);
891 } else {
892 mHandler.sendMessageDelayed(
893 mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
894 DELAY_BEFORE_PREVIEW);
895 }
896 }
897 }
898 }
899
900 private void showKey(final int keyIndex) {
901 final PopupWindow previewPopup = mPreviewPopup;
902 final Key[] keys = mKeys;
Amith Yamasaniebe3d512009-09-17 11:27:14 -0700903 if (keyIndex < 0 || keyIndex >= mKeys.length) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904 Key key = keys[keyIndex];
905 if (key.icon != null) {
906 mPreviewText.setCompoundDrawables(null, null, null,
907 key.iconPreview != null ? key.iconPreview : key.icon);
908 mPreviewText.setText(null);
909 } else {
910 mPreviewText.setCompoundDrawables(null, null, null, null);
911 mPreviewText.setText(getPreviewText(key));
912 if (key.label.length() > 1 && key.codes.length < 2) {
Amith Yamasanie4037002009-07-23 17:38:15 -0700913 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800914 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
915 } else {
Amith Yamasanie4037002009-07-23 17:38:15 -0700916 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917 mPreviewText.setTypeface(Typeface.DEFAULT);
918 }
919 }
920 mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
921 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
922 int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
923 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
924 final int popupHeight = mPreviewHeight;
925 LayoutParams lp = mPreviewText.getLayoutParams();
926 if (lp != null) {
927 lp.width = popupWidth;
928 lp.height = popupHeight;
929 }
930 if (!mPreviewCentered) {
931 mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
932 mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
933 } else {
934 // TODO: Fix this if centering is brought back
935 mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
936 mPopupPreviewY = - mPreviewText.getMeasuredHeight();
937 }
938 mHandler.removeMessages(MSG_REMOVE_PREVIEW);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900939 getLocationInWindow(mCoordinates);
940 mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
941 mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
942
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943 // Set the preview background state
944 mPreviewText.getBackground().setState(
945 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900946 mPopupPreviewX += mCoordinates[0];
947 mPopupPreviewY += mCoordinates[1];
Amith Yamasani42973a42010-03-19 17:21:44 -0700948
949 // If the popup cannot be shown above the key, put it on the side
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +0900950 getLocationOnScreen(mCoordinates);
951 if (mPopupPreviewY + mCoordinates[1] < 0) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700952 // If the key you're pressing is on the left side of the keyboard, show the popup on
953 // the right, offset by enough to see at least one key to the left/right.
954 if (key.x + key.width <= getWidth() / 2) {
955 mPopupPreviewX += (int) (key.width * 2.5);
956 } else {
957 mPopupPreviewX -= (int) (key.width * 2.5);
958 }
959 mPopupPreviewY += popupHeight;
960 }
961
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962 if (previewPopup.isShowing()) {
Amith Yamasani42973a42010-03-19 17:21:44 -0700963 previewPopup.update(mPopupPreviewX, mPopupPreviewY,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 popupWidth, popupHeight);
965 } else {
966 previewPopup.setWidth(popupWidth);
967 previewPopup.setHeight(popupHeight);
968 previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
Amith Yamasani42973a42010-03-19 17:21:44 -0700969 mPopupPreviewX, mPopupPreviewY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800970 }
971 mPreviewText.setVisibility(VISIBLE);
972 }
973
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700974 private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700975 if (mAccessibilityManager.isEnabled()) {
976 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
977 onInitializeAccessibilityEvent(event);
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -0700978 String text = null;
Svetoslav Ganov55f937a2011-12-05 11:42:07 -0800979 // This is very efficient since the properties are cached.
980 final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(),
981 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
982 // Add text only if password announcement is enabled or if headset is
983 // used to avoid leaking passwords.
984 if (speakPassword || mAudioManager.isBluetoothA2dpOn()
985 || mAudioManager.isWiredHeadsetOn()) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -0700986 switch (code) {
987 case Keyboard.KEYCODE_ALT:
988 text = mContext.getString(R.string.keyboardview_keycode_alt);
989 break;
990 case Keyboard.KEYCODE_CANCEL:
991 text = mContext.getString(R.string.keyboardview_keycode_cancel);
992 break;
993 case Keyboard.KEYCODE_DELETE:
994 text = mContext.getString(R.string.keyboardview_keycode_delete);
995 break;
996 case Keyboard.KEYCODE_DONE:
997 text = mContext.getString(R.string.keyboardview_keycode_done);
998 break;
999 case Keyboard.KEYCODE_MODE_CHANGE:
1000 text = mContext.getString(R.string.keyboardview_keycode_mode_change);
1001 break;
1002 case Keyboard.KEYCODE_SHIFT:
1003 text = mContext.getString(R.string.keyboardview_keycode_shift);
1004 break;
1005 case '\n':
1006 text = mContext.getString(R.string.keyboardview_keycode_enter);
1007 break;
1008 default:
1009 text = String.valueOf((char) code);
1010 }
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001011 } else if (!mHeadsetRequiredToHearPasswordsAnnounced) {
1012 // We want the waring for required head set to be send with both the
1013 // hover enter and hover exit event, so set the flag after the exit.
1014 if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
1015 mHeadsetRequiredToHearPasswordsAnnounced = true;
1016 }
1017 text = mContext.getString(R.string.keyboard_headset_required_to_hear_password);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001018 } else {
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001019 text = mContext.getString(R.string.keyboard_password_character_no_headset);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001020 }
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001021 event.getText().add(text);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001022 mAccessibilityManager.sendAccessibilityEvent(event);
1023 }
1024 }
1025
Amith Yamasanif04da952009-05-06 15:46:00 -07001026 /**
1027 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
1028 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
1029 * draws the cached buffer.
1030 * @see #invalidateKey(int)
1031 */
1032 public void invalidateAllKeys() {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001033 mDirtyRect.union(0, 0, getWidth(), getHeight());
1034 mDrawPending = true;
1035 invalidate();
1036 }
Amith Yamasanif04da952009-05-06 15:46:00 -07001037
1038 /**
1039 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1040 * one key is changing it's content. Any changes that affect the position or size of the key
1041 * may not be honored.
1042 * @param keyIndex the index of the key in the attached {@link Keyboard}.
1043 * @see #invalidateAllKeys
1044 */
1045 public void invalidateKey(int keyIndex) {
1046 if (mKeys == null) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 if (keyIndex < 0 || keyIndex >= mKeys.length) {
1048 return;
1049 }
1050 final Key key = mKeys[keyIndex];
1051 mInvalidatedKey = key;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001052 mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
1053 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1054 onBufferDraw();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
1056 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1057 }
1058
1059 private boolean openPopupIfRequired(MotionEvent me) {
1060 // Check if we have a popup layout specified first.
1061 if (mPopupLayout == 0) {
1062 return false;
1063 }
1064 if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
1065 return false;
1066 }
1067
1068 Key popupKey = mKeys[mCurrentKey];
1069 boolean result = onLongPress(popupKey);
1070 if (result) {
1071 mAbortKey = true;
1072 showPreview(NOT_A_KEY);
1073 }
1074 return result;
1075 }
1076
1077 /**
1078 * Called when a key is long pressed. By default this will open any popup keyboard associated
1079 * with this key through the attributes popupLayout and popupCharacters.
1080 * @param popupKey the key that was long pressed
1081 * @return true if the long press is handled, false otherwise. Subclasses should call the
1082 * method on the base class if the subclass doesn't wish to handle the call.
1083 */
1084 protected boolean onLongPress(Key popupKey) {
1085 int popupKeyboardId = popupKey.popupResId;
1086
1087 if (popupKeyboardId != 0) {
1088 mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
1089 if (mMiniKeyboardContainer == null) {
1090 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
1091 Context.LAYOUT_INFLATER_SERVICE);
1092 mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
1093 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1094 com.android.internal.R.id.keyboardView);
1095 View closeButton = mMiniKeyboardContainer.findViewById(
Dianne Hackborn88fb1062009-03-27 14:59:22 -07001096 com.android.internal.R.id.closeButton);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097 if (closeButton != null) closeButton.setOnClickListener(this);
1098 mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
1099 public void onKey(int primaryCode, int[] keyCodes) {
1100 mKeyboardActionListener.onKey(primaryCode, keyCodes);
1101 dismissPopupKeyboard();
1102 }
1103
1104 public void onText(CharSequence text) {
1105 mKeyboardActionListener.onText(text);
1106 dismissPopupKeyboard();
1107 }
1108
1109 public void swipeLeft() { }
1110 public void swipeRight() { }
1111 public void swipeUp() { }
1112 public void swipeDown() { }
1113 public void onPress(int primaryCode) {
1114 mKeyboardActionListener.onPress(primaryCode);
1115 }
1116 public void onRelease(int primaryCode) {
1117 mKeyboardActionListener.onRelease(primaryCode);
1118 }
1119 });
1120 //mInputView.setSuggest(mSuggest);
1121 Keyboard keyboard;
1122 if (popupKey.popupCharacters != null) {
1123 keyboard = new Keyboard(getContext(), popupKeyboardId,
1124 popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
1125 } else {
1126 keyboard = new Keyboard(getContext(), popupKeyboardId);
1127 }
1128 mMiniKeyboard.setKeyboard(keyboard);
1129 mMiniKeyboard.setPopupParent(this);
1130 mMiniKeyboardContainer.measure(
1131 MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
1132 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
1133
1134 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
1135 } else {
1136 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1137 com.android.internal.R.id.keyboardView);
1138 }
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001139 getLocationInWindow(mCoordinates);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001140 mPopupX = popupKey.x + mPaddingLeft;
1141 mPopupY = popupKey.y + mPaddingTop;
1142 mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
1143 mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
Tadashi G. Takaoka21334fd2011-04-20 16:02:37 +09001144 final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
1145 final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146 mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
1147 mMiniKeyboard.setShifted(isShifted());
1148 mPopupKeyboard.setContentView(mMiniKeyboardContainer);
1149 mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
1150 mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
1151 mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
1152 mMiniKeyboardOnScreen = true;
1153 //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
Amith Yamasanif04da952009-05-06 15:46:00 -07001154 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 return true;
1156 }
1157 return false;
1158 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001159
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001160 @Override
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001161 public boolean onHoverEvent(MotionEvent event) {
Svetoslav Ganovbd39ca72011-10-04 17:05:51 -07001162 if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001163 final int action = event.getAction();
1164 switch (action) {
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -07001165 case MotionEvent.ACTION_HOVER_ENTER: {
1166 event.setAction(MotionEvent.ACTION_DOWN);
1167 } break;
1168 case MotionEvent.ACTION_HOVER_MOVE: {
1169 event.setAction(MotionEvent.ACTION_MOVE);
1170 } break;
1171 case MotionEvent.ACTION_HOVER_EXIT: {
1172 event.setAction(MotionEvent.ACTION_UP);
1173 } break;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001174 }
Svetoslav Ganovb6a0b092012-05-22 19:46:57 -07001175 return onTouchEvent(event);
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001176 }
Svetoslav Ganov67578612011-11-01 16:29:45 -07001177 return true;
Svetoslav Ganove8b8e1a2011-08-21 15:06:28 -07001178 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001179
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180 @Override
Svetoslav Ganov67578612011-11-01 16:29:45 -07001181 public boolean onTouchEvent(MotionEvent me) {
Amith Yamasanie877ec62009-08-05 21:12:07 -07001182 // Convert multi-pointer up/down events to single up/down events to
1183 // deal with the typical multi-pointer behavior of two-thumb typing
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001184 final int pointerCount = me.getPointerCount();
1185 final int action = me.getAction();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001186 boolean result = false;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001187 final long now = me.getEventTime();
1188
Amith Yamasanie877ec62009-08-05 21:12:07 -07001189 if (pointerCount != mOldPointerCount) {
Amith Yamasanie877ec62009-08-05 21:12:07 -07001190 if (pointerCount == 1) {
1191 // Send a down event for the latest pointer
1192 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
1193 me.getX(), me.getY(), me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001194 result = onModifiedTouchEvent(down, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001195 down.recycle();
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001196 // If it's an up action, then deliver the up as well.
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001197 if (action == MotionEvent.ACTION_UP) {
1198 result = onModifiedTouchEvent(me, true);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001199 }
Amith Yamasanie877ec62009-08-05 21:12:07 -07001200 } else {
1201 // Send an up event for the last pointer
1202 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
1203 mOldPointerX, mOldPointerY, me.getMetaState());
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001204 result = onModifiedTouchEvent(up, true);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001205 up.recycle();
1206 }
1207 } else {
1208 if (pointerCount == 1) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001209 result = onModifiedTouchEvent(me, false);
Amith Yamasanie877ec62009-08-05 21:12:07 -07001210 mOldPointerX = me.getX();
1211 mOldPointerY = me.getY();
Amith Yamasanie877ec62009-08-05 21:12:07 -07001212 } else {
1213 // Don't do anything when 2 pointers are down and moving.
1214 result = true;
1215 }
1216 }
1217 mOldPointerCount = pointerCount;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001218
Amith Yamasanie877ec62009-08-05 21:12:07 -07001219 return result;
1220 }
1221
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001222 private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
Amith Yamasani678cdbe2009-10-27 11:45:08 -07001223 int touchX = (int) me.getX() - mPaddingLeft;
Tadashi G. Takaoka0972d072010-07-30 15:18:56 -07001224 int touchY = (int) me.getY() - mPaddingTop;
1225 if (touchY >= -mVerticalCorrection)
1226 touchY += mVerticalCorrection;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001227 final int action = me.getAction();
1228 final long eventTime = me.getEventTime();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229 int keyIndex = getKeyIndices(touchX, touchY, null);
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001230 mPossiblePoly = possiblePoly;
1231
1232 // Track the last few movements to look for spurious swipes.
1233 if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
1234 mSwipeTracker.addMovement(me);
1235
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001236 // Ignore all motion events until a DOWN.
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001237 if (mAbortKey
1238 && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001239 return true;
1240 }
1241
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001242 if (mGestureDetector.onTouchEvent(me)) {
1243 showPreview(NOT_A_KEY);
1244 mHandler.removeMessages(MSG_REPEAT);
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001245 mHandler.removeMessages(MSG_LONGPRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 return true;
1247 }
1248
1249 // Needs to be called after the gesture detector gets a turn, as it may have
1250 // displayed the mini keyboard
Amith Yamasani0743a382009-12-08 14:18:19 -08001251 if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 return true;
1253 }
1254
1255 switch (action) {
1256 case MotionEvent.ACTION_DOWN:
1257 mAbortKey = false;
1258 mStartX = touchX;
1259 mStartY = touchY;
1260 mLastCodeX = touchX;
1261 mLastCodeY = touchY;
1262 mLastKeyTime = 0;
1263 mCurrentKeyTime = 0;
1264 mLastKey = NOT_A_KEY;
1265 mCurrentKey = keyIndex;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001266 mDownKey = keyIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267 mDownTime = me.getEventTime();
1268 mLastMoveTime = mDownTime;
1269 checkMultiTap(eventTime, keyIndex);
1270 mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
1271 mKeys[keyIndex].codes[0] : 0);
1272 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
1273 mRepeatKeyIndex = mCurrentKey;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 Message msg = mHandler.obtainMessage(MSG_REPEAT);
1275 mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
Amith Yamasani4d2c27b2010-01-05 14:06:18 -08001276 repeatKey();
1277 // Delivering the key could have caused an abort
1278 if (mAbortKey) {
1279 mRepeatKeyIndex = NOT_A_KEY;
1280 break;
1281 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 }
1283 if (mCurrentKey != NOT_A_KEY) {
1284 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1285 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1286 }
1287 showPreview(keyIndex);
1288 break;
1289
1290 case MotionEvent.ACTION_MOVE:
1291 boolean continueLongPress = false;
1292 if (keyIndex != NOT_A_KEY) {
1293 if (mCurrentKey == NOT_A_KEY) {
1294 mCurrentKey = keyIndex;
1295 mCurrentKeyTime = eventTime - mDownTime;
1296 } else {
1297 if (keyIndex == mCurrentKey) {
1298 mCurrentKeyTime += eventTime - mLastMoveTime;
1299 continueLongPress = true;
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001300 } else if (mRepeatKeyIndex == NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001301 resetMultiTap();
1302 mLastKey = mCurrentKey;
1303 mLastCodeX = mLastX;
1304 mLastCodeY = mLastY;
1305 mLastKeyTime =
1306 mCurrentKeyTime + eventTime - mLastMoveTime;
1307 mCurrentKey = keyIndex;
1308 mCurrentKeyTime = 0;
1309 }
1310 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001311 }
1312 if (!continueLongPress) {
1313 // Cancel old longpress
1314 mHandler.removeMessages(MSG_LONGPRESS);
1315 // Start new longpress if key has changed
1316 if (keyIndex != NOT_A_KEY) {
1317 Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1318 mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1319 }
1320 }
Amith Yamasani8b37eb02009-08-19 20:05:43 -07001321 showPreview(mCurrentKey);
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001322 mLastMoveTime = eventTime;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 break;
1324
1325 case MotionEvent.ACTION_UP:
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001326 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 if (keyIndex == mCurrentKey) {
1328 mCurrentKeyTime += eventTime - mLastMoveTime;
1329 } else {
1330 resetMultiTap();
1331 mLastKey = mCurrentKey;
1332 mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
1333 mCurrentKey = keyIndex;
1334 mCurrentKeyTime = 0;
1335 }
Amith Yamasani7aa936e2010-02-11 18:51:25 -08001336 if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
1337 && mLastKey != NOT_A_KEY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001338 mCurrentKey = mLastKey;
1339 touchX = mLastCodeX;
1340 touchY = mLastCodeY;
1341 }
1342 showPreview(NOT_A_KEY);
1343 Arrays.fill(mKeyIndices, NOT_A_KEY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 // If we're not on a repeating key (which sends on a DOWN event)
1345 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001346 detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001348 invalidateKey(keyIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349 mRepeatKeyIndex = NOT_A_KEY;
1350 break;
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001351 case MotionEvent.ACTION_CANCEL:
1352 removeMessages();
Amith Yamasani0743a382009-12-08 14:18:19 -08001353 dismissPopupKeyboard();
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001354 mAbortKey = true;
1355 showPreview(NOT_A_KEY);
1356 invalidateKey(mCurrentKey);
1357 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358 }
1359 mLastX = touchX;
1360 mLastY = touchY;
1361 return true;
1362 }
1363
1364 private boolean repeatKey() {
1365 Key key = mKeys[mRepeatKeyIndex];
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001366 detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001367 return true;
1368 }
1369
1370 protected void swipeRight() {
1371 mKeyboardActionListener.swipeRight();
1372 }
1373
1374 protected void swipeLeft() {
1375 mKeyboardActionListener.swipeLeft();
1376 }
1377
1378 protected void swipeUp() {
1379 mKeyboardActionListener.swipeUp();
1380 }
1381
1382 protected void swipeDown() {
1383 mKeyboardActionListener.swipeDown();
1384 }
1385
1386 public void closing() {
1387 if (mPreviewPopup.isShowing()) {
1388 mPreviewPopup.dismiss();
1389 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001390 removeMessages();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391
1392 dismissPopupKeyboard();
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001393 mBuffer = null;
1394 mCanvas = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395 mMiniKeyboardCache.clear();
1396 }
Amith Yamasaniebe3d512009-09-17 11:27:14 -07001397
1398 private void removeMessages() {
1399 mHandler.removeMessages(MSG_REPEAT);
1400 mHandler.removeMessages(MSG_LONGPRESS);
1401 mHandler.removeMessages(MSG_SHOW_PREVIEW);
1402 }
1403
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404 @Override
1405 public void onDetachedFromWindow() {
1406 super.onDetachedFromWindow();
1407 closing();
1408 }
1409
1410 private void dismissPopupKeyboard() {
1411 if (mPopupKeyboard.isShowing()) {
1412 mPopupKeyboard.dismiss();
1413 mMiniKeyboardOnScreen = false;
Amith Yamasanif04da952009-05-06 15:46:00 -07001414 invalidateAllKeys();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 }
1416 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07001417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001418 public boolean handleBack() {
1419 if (mPopupKeyboard.isShowing()) {
1420 dismissPopupKeyboard();
1421 return true;
1422 }
1423 return false;
1424 }
1425
1426 private void resetMultiTap() {
1427 mLastSentIndex = NOT_A_KEY;
1428 mTapCount = 0;
1429 mLastTapTime = -1;
1430 mInMultiTap = false;
1431 }
1432
1433 private void checkMultiTap(long eventTime, int keyIndex) {
1434 if (keyIndex == NOT_A_KEY) return;
1435 Key key = mKeys[keyIndex];
1436 if (key.codes.length > 1) {
1437 mInMultiTap = true;
1438 if (eventTime < mLastTapTime + MULTITAP_INTERVAL
1439 && keyIndex == mLastSentIndex) {
1440 mTapCount = (mTapCount + 1) % key.codes.length;
1441 return;
1442 } else {
1443 mTapCount = -1;
1444 return;
1445 }
1446 }
1447 if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
1448 resetMultiTap();
1449 }
1450 }
Amith Yamasaniaea1b3b2009-10-21 16:53:31 -07001451
1452 private static class SwipeTracker {
1453
1454 static final int NUM_PAST = 4;
1455 static final int LONGEST_PAST_TIME = 200;
1456
1457 final float mPastX[] = new float[NUM_PAST];
1458 final float mPastY[] = new float[NUM_PAST];
1459 final long mPastTime[] = new long[NUM_PAST];
1460
1461 float mYVelocity;
1462 float mXVelocity;
1463
1464 public void clear() {
1465 mPastTime[0] = 0;
1466 }
1467
1468 public void addMovement(MotionEvent ev) {
1469 long time = ev.getEventTime();
1470 final int N = ev.getHistorySize();
1471 for (int i=0; i<N; i++) {
1472 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
1473 ev.getHistoricalEventTime(i));
1474 }
1475 addPoint(ev.getX(), ev.getY(), time);
1476 }
1477
1478 private void addPoint(float x, float y, long time) {
1479 int drop = -1;
1480 int i;
1481 final long[] pastTime = mPastTime;
1482 for (i=0; i<NUM_PAST; i++) {
1483 if (pastTime[i] == 0) {
1484 break;
1485 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
1486 drop = i;
1487 }
1488 }
1489 if (i == NUM_PAST && drop < 0) {
1490 drop = 0;
1491 }
1492 if (drop == i) drop--;
1493 final float[] pastX = mPastX;
1494 final float[] pastY = mPastY;
1495 if (drop >= 0) {
1496 final int start = drop+1;
1497 final int count = NUM_PAST-drop-1;
1498 System.arraycopy(pastX, start, pastX, 0, count);
1499 System.arraycopy(pastY, start, pastY, 0, count);
1500 System.arraycopy(pastTime, start, pastTime, 0, count);
1501 i -= (drop+1);
1502 }
1503 pastX[i] = x;
1504 pastY[i] = y;
1505 pastTime[i] = time;
1506 i++;
1507 if (i < NUM_PAST) {
1508 pastTime[i] = 0;
1509 }
1510 }
1511
1512 public void computeCurrentVelocity(int units) {
1513 computeCurrentVelocity(units, Float.MAX_VALUE);
1514 }
1515
1516 public void computeCurrentVelocity(int units, float maxVelocity) {
1517 final float[] pastX = mPastX;
1518 final float[] pastY = mPastY;
1519 final long[] pastTime = mPastTime;
1520
1521 final float oldestX = pastX[0];
1522 final float oldestY = pastY[0];
1523 final long oldestTime = pastTime[0];
1524 float accumX = 0;
1525 float accumY = 0;
1526 int N=0;
1527 while (N < NUM_PAST) {
1528 if (pastTime[N] == 0) {
1529 break;
1530 }
1531 N++;
1532 }
1533
1534 for (int i=1; i < N; i++) {
1535 final int dur = (int)(pastTime[i] - oldestTime);
1536 if (dur == 0) continue;
1537 float dist = pastX[i] - oldestX;
1538 float vel = (dist/dur) * units; // pixels/frame.
1539 if (accumX == 0) accumX = vel;
1540 else accumX = (accumX + vel) * .5f;
1541
1542 dist = pastY[i] - oldestY;
1543 vel = (dist/dur) * units; // pixels/frame.
1544 if (accumY == 0) accumY = vel;
1545 else accumY = (accumY + vel) * .5f;
1546 }
1547 mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
1548 : Math.min(accumX, maxVelocity);
1549 mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
1550 : Math.min(accumY, maxVelocity);
1551 }
1552
1553 public float getXVelocity() {
1554 return mXVelocity;
1555 }
1556
1557 public float getYVelocity() {
1558 return mYVelocity;
1559 }
1560 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001561}