PageRenderTime 61ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/LatinKeyboardBaseView.java

http://eyes-free.googlecode.com/
Java | 1809 lines | 1220 code | 204 blank | 385 comment | 255 complexity | 0ac10b54221dc7e48a00a88aa1d3d2b7 MD5 | raw file
Possible License(s): GPL-3.0, Apache-2.0
  1. /*
  2. * Copyright (C) 2010 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.googlecode.eyesfree.inputmethod.latin;
  17. import android.content.Context;
  18. import android.content.pm.PackageManager;
  19. import android.content.res.Resources;
  20. import android.content.res.TypedArray;
  21. import android.graphics.Bitmap;
  22. import android.graphics.Canvas;
  23. import android.graphics.Paint;
  24. import android.graphics.Paint.Align;
  25. import android.graphics.PorterDuff;
  26. import android.graphics.Rect;
  27. import android.graphics.Region.Op;
  28. import android.graphics.Typeface;
  29. import android.graphics.drawable.Drawable;
  30. import android.inputmethodservice.Keyboard;
  31. import android.inputmethodservice.Keyboard.Key;
  32. import android.os.Build;
  33. import android.os.Handler;
  34. import android.os.Message;
  35. import android.os.SystemClock;
  36. import android.util.AttributeSet;
  37. import android.util.Log;
  38. import android.util.TypedValue;
  39. import android.view.Gravity;
  40. import android.view.KeyEvent;
  41. import android.view.LayoutInflater;
  42. import android.view.MotionEvent;
  43. import android.view.View;
  44. import android.view.ViewConfiguration;
  45. import android.view.ViewGroup.LayoutParams;
  46. import android.widget.PopupWindow;
  47. import android.widget.TextView;
  48. import com.googlecode.eyesfree.inputmethod.MultitouchGestureDetector;
  49. import com.googlecode.eyesfree.inputmethod.SegmentDetector;
  50. import com.googlecode.eyesfree.inputmethod.SegmentDetector.SegmentListener;
  51. import com.googlecode.eyesfree.inputmethod.SimpleMultitouchGestureListener;
  52. import com.googlecode.eyesfree.utils.compat.MotionEventCompatUtils;
  53. import java.util.ArrayList;
  54. import java.util.HashMap;
  55. import java.util.LinkedList;
  56. import java.util.List;
  57. import java.util.WeakHashMap;
  58. /**
  59. * A view that renders a virtual {@link LatinKeyboard}. It handles rendering of keys and detecting
  60. * key presses and touch movements. TODO: References to LatinKeyboard in this class should be
  61. * replaced with ones to its base class.
  62. *
  63. * @attr ref R.styleable#LatinKeyboardBaseView_keyBackground
  64. * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewLayout
  65. * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewOffset
  66. * @attr ref R.styleable#LatinKeyboardBaseView_labelTextSize
  67. * @attr ref R.styleable#LatinKeyboardBaseView_keyTextSize
  68. * @attr ref R.styleable#LatinKeyboardBaseView_keyTextColor
  69. * @attr ref R.styleable#LatinKeyboardBaseView_verticalCorrection
  70. * @attr ref R.styleable#LatinKeyboardBaseView_popupLayout
  71. */
  72. public class LatinKeyboardBaseView extends View implements PointerTracker.UIProxy {
  73. private static final String TAG = "LatinKeyboardBaseView";
  74. private static final boolean DEBUG = false;
  75. public static final int NOT_A_TOUCH_COORDINATE = -1;
  76. public interface OnKeyboardActionListener {
  77. /**
  78. * Called when the user presses a key. This is sent before the {@link #onKey} is called. For
  79. * keys that repeat, this is only called once.
  80. *
  81. * @param key the key being pressed
  82. */
  83. void onPress(Key key);
  84. /**
  85. * Called when the user leaves a key without lifting their finger. This is send before the
  86. * {@link #onKey} is called. For keys that repeat, this is only called once.
  87. *
  88. * @param key the key that was left
  89. */
  90. void onLeaving(Key key);
  91. /**
  92. * Called when the user releases a key. This is sent after the {@link #onKey} is called. For
  93. * keys that repeat, this is only called once.
  94. *
  95. * @param key the key that was released
  96. */
  97. void onRelease(Key key);
  98. /**
  99. * Send a key press to the listener.
  100. *
  101. * @param primaryCode this is the key that was pressed
  102. * @param keyCodes the codes for all the possible alternative keys with the primary code
  103. * being the first. If the primary key code is a single character such as an
  104. * alphabet or number or symbol, the alternatives will include other characters
  105. * that may be on the same key or adjacent keys. These codes are useful to
  106. * correct for accidental presses of a key adjacent to the intended key.
  107. * @param x x-coordinate pixel of touched event. If onKey is not called by onTouchEvent, the
  108. * value should be NOT_A_TOUCH_COORDINATE.
  109. * @param y y-coordinate pixel of touched event. If onKey is not called by onTouchEvent, the
  110. * value should be NOT_A_TOUCH_COORDINATE.
  111. */
  112. void onKey(int primaryCode, int[] keyCodes, int x, int y);
  113. /**
  114. * Sends a sequence of characters to the listener.
  115. *
  116. * @param text the sequence of characters to be displayed.
  117. */
  118. void onText(CharSequence text);
  119. /**
  120. * Called when user released a finger outside any key.
  121. */
  122. void onCancel();
  123. /**
  124. * Called when the user quickly moves the finger from right to left.
  125. *
  126. * @param pointerCount
  127. * @return <code>true</code> if the event was consumed.
  128. */
  129. boolean swipeLeft(int pointerCount);
  130. /**
  131. * Called when the user quickly moves the finger from left to right.
  132. *
  133. * @param pointerCount
  134. * @return <code>true</code> if the event was consumed.
  135. */
  136. boolean swipeRight(int pointerCount);
  137. /**
  138. * Called when the user quickly moves the finger from up to down.
  139. *
  140. * @param pointerCount
  141. * @return <code>true</code> if the event was consumed.
  142. */
  143. boolean swipeDown(int pointerCount);
  144. /**
  145. * Called when the user quickly moves the finger from down to up.
  146. *
  147. * @param pointerCount
  148. * @return <code>true</code> if the event was consumed.
  149. */
  150. boolean swipeUp(int pointerCount);
  151. /**
  152. * Called when the user quickly taps the finger on screen.
  153. *
  154. * @param pointerCount
  155. * @return <code>true</code> if the event was consumed.
  156. */
  157. boolean singleTap(int pointerCount);
  158. /**
  159. * Called when the user double taps the finger on screen.
  160. *
  161. * @param pointerCount
  162. * @return <code>true</code> if the event was consumed.
  163. */
  164. boolean doubleTap(int pointerCount);
  165. /**
  166. * Called when the user long-presses a finger on screen.
  167. *
  168. * @param pointerCount
  169. * @return <code>true</code> if the event was consumed.
  170. */
  171. boolean longPress(int pointerCount);
  172. /**
  173. * Called when the user moves the finger outside the keyboard area.
  174. */
  175. void exploreKeyboardArea();
  176. /**
  177. * Called when the user moves the finger outside the keyboard area.
  178. */
  179. void enteredSegment(int segment);
  180. /**
  181. * Called when the user selects a segment.
  182. */
  183. void selectedSegment(int segment);
  184. /**
  185. * Called when the user moves the finger outside the keyboard area.
  186. */
  187. void leftKeyboardArea();
  188. /**
  189. * Called when the user moves the finger outside the keyboard area, then back inside the
  190. * keyboard area.
  191. */
  192. void enteredKeyboardArea();
  193. /**
  194. * Called when a finger is lifted outside the keyboard area.
  195. */
  196. void upOutsideKeyboardArea();
  197. }
  198. // Timing constants
  199. private final int mKeyRepeatInterval;
  200. // Miscellaneous constants
  201. /* package */static final int NOT_A_KEY = -1;
  202. private static final int[] LONG_PRESSABLE_STATE_SET = {
  203. android.R.attr.state_long_pressable
  204. };
  205. private static final int NUMBER_HINT_VERTICAL_ADJUSTMENT_PIXEL = -1;
  206. // XML attribute
  207. private int mKeyTextSize;
  208. private int mKeyTextColor;
  209. private Typeface mKeyTextStyle = Typeface.DEFAULT;
  210. private int mLabelTextSize;
  211. private int mSymbolColorScheme = 0;
  212. private int mShadowColor;
  213. private float mShadowRadius;
  214. private Drawable mKeyBackground;
  215. private float mBackgroundDimAmount;
  216. private float mKeyHysteresisDistance;
  217. private float mVerticalCorrection;
  218. private int mPreviewOffset;
  219. private int mPreviewHeight;
  220. private int mPopupLayout;
  221. // Main keyboard
  222. private Keyboard mKeyboard;
  223. private Key[] mKeys;
  224. // TODO this attribute should be gotten from Keyboard.
  225. private int mKeyboardVerticalGap;
  226. // Key preview popup
  227. private TextView mPreviewText;
  228. private PopupWindow mPreviewPopup;
  229. private int mPreviewTextSizeLarge;
  230. private int[] mOffsetInWindow;
  231. private int mOldPreviewKeyIndex = NOT_A_KEY;
  232. private boolean mShowPreview = true;
  233. private boolean mShowTouchPoints = true;
  234. private int mPopupPreviewOffsetX;
  235. private int mPopupPreviewOffsetY;
  236. private int mWindowY;
  237. private int mPopupPreviewDisplayedY;
  238. private final int mDelayBeforePreview;
  239. private final int mDelayAfterPreview;
  240. // Popup mini keyboard
  241. private PopupWindow mMiniKeyboardPopup;
  242. private LatinKeyboardBaseView mMiniKeyboard;
  243. private View mMiniKeyboardParent;
  244. private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
  245. private int mMiniKeyboardOriginX;
  246. private int mMiniKeyboardOriginY;
  247. private long mMiniKeyboardPopupTime;
  248. private int[] mWindowOffset;
  249. private final float mMiniKeyboardSlideAllowance;
  250. private int mMiniKeyboardTrackerId;
  251. /** Listener for {@link OnKeyboardActionListener}. */
  252. private OnKeyboardActionListener mKeyboardActionListener;
  253. private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
  254. // TODO: Let the PointerTracker class manage this pointer queue
  255. private final PointerQueue mPointerQueue = new PointerQueue();
  256. private final boolean mHasDistinctMultitouch;
  257. private int mOldPointerCount = 1;
  258. // Accessibility
  259. private final int mTouchSlop;
  260. private boolean mIsAccessibilityEnabled;
  261. private boolean mInKeyboardArea = false;
  262. protected KeyDetector mKeyDetector = new ProximityKeyDetector();
  263. // Segment detector
  264. private SegmentDetector mSegmentDetector;
  265. // Swipe gesture detector
  266. private MultitouchGestureDetector mGestureDetector;
  267. // Drawing
  268. /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
  269. private boolean mDrawPending;
  270. /** The dirty region in the keyboard bitmap */
  271. private final Rect mDirtyRect = new Rect();
  272. /** The keyboard bitmap for faster updates */
  273. private Bitmap mBuffer;
  274. /**
  275. * Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer.
  276. */
  277. private boolean mKeyboardChanged;
  278. private Key mInvalidatedKey;
  279. /** The canvas for the above mutable keyboard bitmap */
  280. private Canvas mCanvas;
  281. private final Paint mPaint;
  282. private final Rect mPadding;
  283. private final Rect mClipRegion = new Rect(0, 0, 0, 0);
  284. // This map caches key label text height in pixel as value and key label
  285. // text size as map key.
  286. private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>();
  287. // Distance from horizontal center of the key, proportional to key label
  288. // text height.
  289. private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR = 0.55f;
  290. private final String KEY_LABEL_HEIGHT_REFERENCE_CHAR = "H";
  291. private final UIHandler mHandler = new UIHandler();
  292. class UIHandler extends Handler {
  293. private static final int MSG_POPUP_PREVIEW = 1;
  294. private static final int MSG_DISMISS_PREVIEW = 2;
  295. private static final int MSG_REPEAT_KEY = 3;
  296. private static final int MSG_LONGPRESS_KEY = 4;
  297. private boolean mInKeyRepeat;
  298. @Override
  299. public void handleMessage(Message msg) {
  300. switch (msg.what) {
  301. case MSG_POPUP_PREVIEW:
  302. showKey(msg.arg1, (PointerTracker) msg.obj);
  303. break;
  304. case MSG_DISMISS_PREVIEW:
  305. mPreviewPopup.dismiss();
  306. break;
  307. case MSG_REPEAT_KEY: {
  308. final PointerTracker tracker = (PointerTracker) msg.obj;
  309. tracker.repeatKey(msg.arg1);
  310. startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker);
  311. break;
  312. }
  313. case MSG_LONGPRESS_KEY: {
  314. final PointerTracker tracker = (PointerTracker) msg.obj;
  315. openPopupIfRequired(msg.arg1, tracker);
  316. break;
  317. }
  318. }
  319. }
  320. public void popupPreview(long delay, int keyIndex, PointerTracker tracker) {
  321. removeMessages(MSG_POPUP_PREVIEW);
  322. if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
  323. // Show right away, if it's already visible and finger is moving
  324. // around
  325. showKey(keyIndex, tracker);
  326. } else {
  327. sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker),
  328. delay);
  329. }
  330. }
  331. public void cancelPopupPreview() {
  332. removeMessages(MSG_POPUP_PREVIEW);
  333. }
  334. public void dismissPreview(long delay) {
  335. if (mPreviewPopup.isShowing()) {
  336. sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
  337. }
  338. }
  339. public void cancelDismissPreview() {
  340. removeMessages(MSG_DISMISS_PREVIEW);
  341. }
  342. public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
  343. mInKeyRepeat = true;
  344. sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
  345. }
  346. public void cancelKeyRepeatTimer() {
  347. mInKeyRepeat = false;
  348. removeMessages(MSG_REPEAT_KEY);
  349. }
  350. public boolean isInKeyRepeat() {
  351. return mInKeyRepeat;
  352. }
  353. public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
  354. // Accessibility disables long press because users are likely to
  355. // need to pause on a key
  356. // for an unspecified duration in order to hear the key's spoken
  357. // description.
  358. if (mIsAccessibilityEnabled) {
  359. return;
  360. }
  361. removeMessages(MSG_LONGPRESS_KEY);
  362. sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
  363. }
  364. public void cancelLongPressTimer() {
  365. removeMessages(MSG_LONGPRESS_KEY);
  366. }
  367. public void cancelKeyTimers() {
  368. cancelKeyRepeatTimer();
  369. cancelLongPressTimer();
  370. }
  371. public void cancelAllMessages() {
  372. cancelKeyTimers();
  373. cancelPopupPreview();
  374. cancelDismissPreview();
  375. }
  376. }
  377. static class PointerQueue {
  378. private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
  379. public void add(PointerTracker tracker) {
  380. mQueue.add(tracker);
  381. }
  382. public int lastIndexOf(PointerTracker tracker) {
  383. LinkedList<PointerTracker> queue = mQueue;
  384. for (int index = queue.size() - 1; index >= 0; index--) {
  385. PointerTracker t = queue.get(index);
  386. if (t == tracker)
  387. return index;
  388. }
  389. return -1;
  390. }
  391. public void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) {
  392. LinkedList<PointerTracker> queue = mQueue;
  393. int oldestPos = 0;
  394. for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
  395. if (t.isModifier()) {
  396. oldestPos++;
  397. } else {
  398. t.onUpEvent(t.getLastX(), t.getLastY(), eventTime);
  399. t.setAlreadyProcessed();
  400. queue.remove(oldestPos);
  401. }
  402. }
  403. }
  404. public void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
  405. for (PointerTracker t : mQueue) {
  406. if (t == tracker)
  407. continue;
  408. t.onUpEvent(t.getLastX(), t.getLastY(), eventTime);
  409. t.setAlreadyProcessed();
  410. }
  411. mQueue.clear();
  412. if (tracker != null)
  413. mQueue.add(tracker);
  414. }
  415. public void remove(PointerTracker tracker) {
  416. mQueue.remove(tracker);
  417. }
  418. }
  419. public LatinKeyboardBaseView(Context context, AttributeSet attrs) {
  420. this(context, attrs, R.attr.keyboardViewStyle);
  421. }
  422. public LatinKeyboardBaseView(Context context, AttributeSet attrs, int defStyle) {
  423. super(context, attrs, defStyle);
  424. TypedArray a = context.obtainStyledAttributes(
  425. attrs, R.styleable.LatinKeyboardBaseView, defStyle, R.style.LatinKeyboardBaseView);
  426. LayoutInflater inflate =
  427. (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  428. int previewLayout = 0;
  429. int keyTextSize = 0;
  430. int n = a.getIndexCount();
  431. for (int i = 0; i < n; i++) {
  432. int attr = a.getIndex(i);
  433. switch (attr) {
  434. case R.styleable.LatinKeyboardBaseView_keyBackground:
  435. mKeyBackground = a.getDrawable(attr);
  436. break;
  437. case R.styleable.LatinKeyboardBaseView_keyHysteresisDistance:
  438. mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0);
  439. break;
  440. case R.styleable.LatinKeyboardBaseView_verticalCorrection:
  441. mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
  442. break;
  443. case R.styleable.LatinKeyboardBaseView_keyPreviewLayout:
  444. previewLayout = a.getResourceId(attr, 0);
  445. break;
  446. case R.styleable.LatinKeyboardBaseView_keyPreviewOffset:
  447. mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
  448. break;
  449. case R.styleable.LatinKeyboardBaseView_keyPreviewHeight:
  450. mPreviewHeight = a.getDimensionPixelSize(attr, 80);
  451. break;
  452. case R.styleable.LatinKeyboardBaseView_keyTextSize:
  453. mKeyTextSize = a.getDimensionPixelSize(attr, 18);
  454. break;
  455. case R.styleable.LatinKeyboardBaseView_keyTextColor:
  456. mKeyTextColor = a.getColor(attr, 0xFF000000);
  457. break;
  458. case R.styleable.LatinKeyboardBaseView_labelTextSize:
  459. mLabelTextSize = a.getDimensionPixelSize(attr, 14);
  460. break;
  461. case R.styleable.LatinKeyboardBaseView_popupLayout:
  462. mPopupLayout = a.getResourceId(attr, 0);
  463. break;
  464. case R.styleable.LatinKeyboardBaseView_shadowColor:
  465. mShadowColor = a.getColor(attr, 0);
  466. break;
  467. case R.styleable.LatinKeyboardBaseView_shadowRadius:
  468. mShadowRadius = a.getFloat(attr, 0f);
  469. break;
  470. // TODO: Use Theme
  471. // (android.R.styleable.Theme_backgroundDimAmount)
  472. case R.styleable.LatinKeyboardBaseView_backgroundDimAmount:
  473. mBackgroundDimAmount = a.getFloat(attr, 0.5f);
  474. break;
  475. // case android.R.styleable.
  476. case R.styleable.LatinKeyboardBaseView_keyTextStyle:
  477. int textStyle = a.getInt(attr, 0);
  478. switch (textStyle) {
  479. case 0:
  480. mKeyTextStyle = Typeface.DEFAULT;
  481. break;
  482. case 1:
  483. mKeyTextStyle = Typeface.DEFAULT_BOLD;
  484. break;
  485. default:
  486. mKeyTextStyle = Typeface.defaultFromStyle(textStyle);
  487. break;
  488. }
  489. break;
  490. case R.styleable.LatinKeyboardBaseView_symbolColorScheme:
  491. mSymbolColorScheme = a.getInt(attr, 0);
  492. break;
  493. }
  494. }
  495. final Resources res = getResources();
  496. mPreviewPopup = new PopupWindow(context);
  497. if (previewLayout != 0) {
  498. mPreviewText = (TextView) inflate.inflate(previewLayout, null);
  499. mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large);
  500. mPreviewPopup.setContentView(mPreviewText);
  501. mPreviewPopup.setBackgroundDrawable(null);
  502. } else {
  503. mShowPreview = false;
  504. }
  505. mPreviewPopup.setTouchable(false);
  506. mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
  507. mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
  508. mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
  509. mMiniKeyboardParent = this;
  510. mMiniKeyboardPopup = new PopupWindow(context);
  511. mMiniKeyboardPopup.setBackgroundDrawable(null);
  512. mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation);
  513. mPaint = new Paint();
  514. mPaint.setAntiAlias(true);
  515. mPaint.setTextSize(keyTextSize);
  516. mPaint.setTextAlign(Align.CENTER);
  517. mPaint.setAlpha(255);
  518. mPadding = new Rect(0, 0, 0, 0);
  519. mKeyBackground.getPadding(mPadding);
  520. mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance);
  521. mSegmentDetector = new SegmentDetector();
  522. mSegmentDetector.setListener(segmentListener);
  523. mSegmentDetector.updateDimensions(this);
  524. mGestureDetector = new MultitouchGestureDetector(context);
  525. mGestureDetector.setListener(multitouchListener);
  526. mGestureDetector.setIsLongPressEnabled(false);
  527. mGestureDetector.setIsDoubleTapEnabled(true);
  528. mGestureDetector.setDoubleTapMinFingers(2);
  529. mHasDistinctMultitouch = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO)
  530. && context.getPackageManager().hasSystemFeature(
  531. PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
  532. mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
  533. mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  534. }
  535. public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
  536. mKeyboardActionListener = listener;
  537. for (PointerTracker tracker : mPointerTrackers) {
  538. tracker.setOnKeyboardActionListener(listener);
  539. }
  540. }
  541. /**
  542. * Returns the {@link OnKeyboardActionListener} object.
  543. *
  544. * @return the listener attached to this keyboard
  545. */
  546. protected OnKeyboardActionListener getOnKeyboardActionListener() {
  547. return mKeyboardActionListener;
  548. }
  549. /**
  550. * Attaches a keyboard to this view. The keyboard can be switched at any time and the view will
  551. * re-layout itself to accommodate the keyboard.
  552. *
  553. * @see Keyboard
  554. * @see #getKeyboard()
  555. * @param keyboard the keyboard to display in this view
  556. */
  557. public void setKeyboard(Keyboard keyboard) {
  558. if (mKeyboard != null) {
  559. dismissKeyPreview();
  560. }
  561. // Remove any pending messages, except dismissing preview
  562. mHandler.cancelKeyTimers();
  563. mHandler.cancelPopupPreview();
  564. mKeyboard = keyboard;
  565. LatinImeLogger.onSetKeyboard(keyboard);
  566. mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
  567. -getPaddingTop() + mVerticalCorrection);
  568. mKeyboardVerticalGap = (int) getResources().getDimension(R.dimen.key_bottom_gap);
  569. for (PointerTracker tracker : mPointerTrackers) {
  570. tracker.setKeyboard(mKeys, mKeyHysteresisDistance);
  571. }
  572. requestLayout();
  573. // Hint to reallocate the buffer if the size changed
  574. mKeyboardChanged = true;
  575. invalidateAllKeys();
  576. computeProximityThreshold(keyboard);
  577. mMiniKeyboardCache.clear();
  578. }
  579. /**
  580. * Returns the current keyboard being displayed by this view.
  581. *
  582. * @return the currently attached keyboard
  583. * @see #setKeyboard(Keyboard)
  584. */
  585. public Keyboard getKeyboard() {
  586. return mKeyboard;
  587. }
  588. /**
  589. * Return whether the device has distinct multi-touch panel.
  590. *
  591. * @return true if the device has distinct multi-touch panel.
  592. */
  593. @Override
  594. public boolean hasDistinctMultitouch() {
  595. return mHasDistinctMultitouch;
  596. }
  597. /**
  598. * Enables or disables accessibility.
  599. *
  600. * @param accessibilityEnabled whether or not to enable accessibility
  601. */
  602. public void setAccessibilityEnabled(boolean accessibilityEnabled) {
  603. mIsAccessibilityEnabled = accessibilityEnabled;
  604. // Propagate this change to all existing pointer trackers.
  605. for (PointerTracker pointerTracker : mPointerTrackers) {
  606. pointerTracker.setAccessibilityEnabled(accessibilityEnabled);
  607. }
  608. }
  609. /**
  610. * Return whether the device has accessibility turned on.
  611. *
  612. * @return true if the device has accessibility turned on.
  613. */
  614. @Override
  615. public boolean isAccessibilityEnabled() {
  616. return mIsAccessibilityEnabled;
  617. }
  618. /**
  619. * Sets the state of the shift key of the keyboard, if any.
  620. *
  621. * @param shifted whether or not to enable the state of the shift key
  622. * @return true if the shift key state changed, false if there was no change
  623. */
  624. public boolean setShifted(boolean shifted) {
  625. if (mKeyboard != null) {
  626. if (mKeyboard.setShifted(shifted)) {
  627. // The whole keyboard probably needs to be redrawn
  628. invalidateAllKeys();
  629. return true;
  630. }
  631. }
  632. return false;
  633. }
  634. /**
  635. * Returns the state of the shift key of the keyboard, if any.
  636. *
  637. * @return true if the shift is in a pressed state, false otherwise. If there is no shift key on
  638. * the keyboard or there is no keyboard attached, it returns false.
  639. */
  640. public boolean isShifted() {
  641. if (mKeyboard != null) {
  642. return mKeyboard.isShifted();
  643. }
  644. return false;
  645. }
  646. /**
  647. * Enables or disables long-press.
  648. *
  649. * @param longPressEnabled whether or not to enable long-press
  650. */
  651. public void setLongPressEnabled(boolean longPressEnabled) {
  652. mGestureDetector.setIsLongPressEnabled(longPressEnabled);
  653. }
  654. /**
  655. * Enables or disables the key feedback popup. This is a popup that shows a magnified version of
  656. * the depressed key. By default the preview is enabled.
  657. *
  658. * @param previewEnabled whether or not to enable the key feedback popup
  659. * @see #isPreviewEnabled()
  660. */
  661. public void setPreviewEnabled(boolean previewEnabled) {
  662. mShowPreview = previewEnabled;
  663. }
  664. /**
  665. * Returns the enabled state of the key feedback popup.
  666. *
  667. * @return whether or not the key feedback popup is enabled
  668. * @see #setPreviewEnabled(boolean)
  669. */
  670. public boolean isPreviewEnabled() {
  671. return mShowPreview;
  672. }
  673. public int getSymbolColorScheme() {
  674. return mSymbolColorScheme;
  675. }
  676. public void setPopupParent(View v) {
  677. mMiniKeyboardParent = v;
  678. }
  679. public void setPopupOffset(int x, int y) {
  680. mPopupPreviewOffsetX = x;
  681. mPopupPreviewOffsetY = y;
  682. mPreviewPopup.dismiss();
  683. }
  684. /**
  685. * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key codes for
  686. * adjacent keys. When disabled, only the primary key code will be reported.
  687. *
  688. * @param enabled whether or not the proximity correction is enabled
  689. */
  690. public void setProximityCorrectionEnabled(boolean enabled) {
  691. mKeyDetector.setProximityCorrectionEnabled(enabled);
  692. }
  693. /**
  694. * Returns true if proximity correction is enabled.
  695. */
  696. public boolean isProximityCorrectionEnabled() {
  697. return mKeyDetector.isProximityCorrectionEnabled();
  698. }
  699. protected CharSequence adjustCase(CharSequence label) {
  700. if (mKeyboard.isShifted() && label != null && label.length() < 3
  701. && Character.isLowerCase(label.charAt(0))) {
  702. label = label.toString().toUpperCase();
  703. }
  704. return label;
  705. }
  706. @Override
  707. public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  708. // Round up a little
  709. if (mKeyboard == null) {
  710. setMeasuredDimension(
  711. getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
  712. } else {
  713. int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
  714. if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
  715. width = MeasureSpec.getSize(widthMeasureSpec);
  716. }
  717. setMeasuredDimension(
  718. getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  719. mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
  720. }
  721. }
  722. /**
  723. * Compute the average distance between adjacent keys (horizontally and vertically) and square
  724. * it to get the proximity threshold. We use a square here and in computing the touch distance
  725. * from a key's center to avoid taking a square root.
  726. *
  727. * @param keyboard
  728. */
  729. private void computeProximityThreshold(Keyboard keyboard) {
  730. if (keyboard == null)
  731. return;
  732. final Key[] keys = mKeys;
  733. if (keys == null)
  734. return;
  735. int length = keys.length;
  736. int dimensionSum = 0;
  737. for (int i = 0; i < length; i++) {
  738. Key key = keys[i];
  739. dimensionSum += Math.min(key.width, key.height + mKeyboardVerticalGap) + key.gap;
  740. }
  741. if (dimensionSum < 0 || length == 0)
  742. return;
  743. mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
  744. }
  745. @Override
  746. public void onSizeChanged(int w, int h, int oldw, int oldh) {
  747. super.onSizeChanged(w, h, oldw, oldh);
  748. // Release the buffer, if any and it will be reallocated on the next
  749. // draw
  750. mBuffer = null;
  751. mSegmentDetector.updateDimensions(this);
  752. }
  753. @Override
  754. public void onDraw(Canvas canvas) {
  755. super.onDraw(canvas);
  756. if (mDrawPending || mBuffer == null || mKeyboardChanged) {
  757. onBufferDraw();
  758. }
  759. canvas.drawBitmap(mBuffer, 0, 0, null);
  760. }
  761. private void onBufferDraw() {
  762. if (mBuffer == null || mKeyboardChanged) {
  763. if (mBuffer == null || mKeyboardChanged &&
  764. (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
  765. // Make sure our bitmap is at least 1x1
  766. final int width = Math.max(1, getWidth());
  767. final int height = Math.max(1, getHeight());
  768. mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  769. mCanvas = new Canvas(mBuffer);
  770. }
  771. invalidateAllKeys();
  772. mKeyboardChanged = false;
  773. }
  774. final Canvas canvas = mCanvas;
  775. canvas.clipRect(mDirtyRect, Op.REPLACE);
  776. if (mKeyboard == null)
  777. return;
  778. final Paint paint = mPaint;
  779. final Drawable keyBackground = mKeyBackground;
  780. final Rect clipRegion = mClipRegion;
  781. final Rect padding = mPadding;
  782. final int kbdPaddingLeft = getPaddingLeft();
  783. final int kbdPaddingTop = getPaddingTop();
  784. final Key[] keys = mKeys;
  785. final Key invalidKey = mInvalidatedKey;
  786. paint.setColor(mKeyTextColor);
  787. boolean drawSingleKey = false;
  788. if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
  789. // TODO we should use Rect.inset and Rect.contains here.
  790. // Is clipRegion completely contained within the invalidated key?
  791. if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
  792. invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
  793. invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
  794. invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
  795. drawSingleKey = true;
  796. }
  797. }
  798. canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
  799. final int keyCount = keys.length;
  800. for (int i = 0; i < keyCount; i++) {
  801. final Key key = keys[i];
  802. if (drawSingleKey && invalidKey != key) {
  803. continue;
  804. }
  805. int[] drawableState = key.getCurrentDrawableState();
  806. keyBackground.setState(drawableState);
  807. // Switch the character to uppercase if shift is pressed
  808. String label = key.label == null ? null : adjustCase(key.label).toString();
  809. final Rect bounds = keyBackground.getBounds();
  810. if (key.width != bounds.right || key.height != bounds.bottom) {
  811. keyBackground.setBounds(0, 0, key.width, key.height);
  812. }
  813. canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
  814. keyBackground.draw(canvas);
  815. boolean shouldDrawIcon = true;
  816. if (label != null) {
  817. // For characters, use large font. For labels like "Done", use
  818. // small font.
  819. final int labelSize;
  820. if (label.length() > 1 && key.codes.length < 2) {
  821. labelSize = mLabelTextSize;
  822. paint.setTypeface(Typeface.DEFAULT_BOLD);
  823. } else {
  824. labelSize = mKeyTextSize;
  825. paint.setTypeface(mKeyTextStyle);
  826. }
  827. paint.setTextSize(labelSize);
  828. Integer labelHeightValue = mTextHeightCache.get(labelSize);
  829. final int labelHeight;
  830. if (labelHeightValue != null) {
  831. labelHeight = labelHeightValue;
  832. } else {
  833. Rect textBounds = new Rect();
  834. paint.getTextBounds(KEY_LABEL_HEIGHT_REFERENCE_CHAR, 0, 1, textBounds);
  835. labelHeight = textBounds.height();
  836. mTextHeightCache.put(labelSize, labelHeight);
  837. }
  838. // Draw a drop shadow for the text
  839. paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
  840. final int centerX = (key.width + padding.left - padding.right) / 2;
  841. final int centerY = (key.height + padding.top - padding.bottom) / 2;
  842. final float baseline = centerY
  843. + labelHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR;
  844. canvas.drawText(label, centerX, baseline, paint);
  845. // Turn off drop shadow
  846. paint.setShadowLayer(0, 0, 0, 0);
  847. // Usually don't draw icon if label is not null, but we draw
  848. // icon for the number
  849. // hint and popup hint.
  850. shouldDrawIcon = shouldDrawLabelAndIcon(key);
  851. }
  852. if (key.icon != null && shouldDrawIcon) {
  853. // Special handing for the upper-right number hint icons
  854. final int drawableWidth;
  855. final int drawableHeight;
  856. final int drawableX;
  857. final int drawableY;
  858. if (shouldDrawIconFully(key)) {
  859. drawableWidth = key.width;
  860. drawableHeight = key.height;
  861. drawableX = 0;
  862. drawableY = NUMBER_HINT_VERTICAL_ADJUSTMENT_PIXEL;
  863. } else {
  864. drawableWidth = key.icon.getIntrinsicWidth();
  865. drawableHeight = key.icon.getIntrinsicHeight();
  866. drawableX = (key.width + padding.left - padding.right - drawableWidth) / 2;
  867. drawableY = (key.height + padding.top - padding.bottom - drawableHeight) / 2;
  868. }
  869. canvas.translate(drawableX, drawableY);
  870. key.icon.setBounds(0, 0, drawableWidth, drawableHeight);
  871. key.icon.draw(canvas);
  872. canvas.translate(-drawableX, -drawableY);
  873. }
  874. canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
  875. }
  876. mInvalidatedKey = null;
  877. // Overlay a dark rectangle to dim the keyboard
  878. if (mMiniKeyboard != null) {
  879. paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
  880. canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
  881. }
  882. if (DEBUG) {
  883. if (mShowTouchPoints) {
  884. for (PointerTracker tracker : mPointerTrackers) {
  885. int startX = tracker.getStartX();
  886. int startY = tracker.getStartY();
  887. int lastX = tracker.getLastX();
  888. int lastY = tracker.getLastY();
  889. paint.setAlpha(128);
  890. paint.setColor(0xFFFF0000);
  891. canvas.drawCircle(startX, startY, 3, paint);
  892. canvas.drawLine(startX, startY, lastX, lastY, paint);
  893. paint.setColor(0xFF0000FF);
  894. canvas.drawCircle(lastX, lastY, 3, paint);
  895. paint.setColor(0xFF00FF00);
  896. canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint);
  897. }
  898. }
  899. }
  900. mDrawPending = false;
  901. mDirtyRect.setEmpty();
  902. }
  903. // TODO: clean up this method.
  904. private void dismissKeyPreview() {
  905. for (PointerTracker tracker : mPointerTrackers)
  906. tracker.updateKey(NOT_A_KEY);
  907. showPreview(NOT_A_KEY, null);
  908. }
  909. @Override
  910. public void showPreview(int keyIndex, PointerTracker tracker) {
  911. int oldKeyIndex = mOldPreviewKeyIndex;
  912. mOldPreviewKeyIndex = keyIndex;
  913. final boolean isLanguageSwitchEnabled = (mKeyboard instanceof LatinKeyboard)
  914. && ((LatinKeyboard) mKeyboard).isLanguageSwitchEnabled();
  915. // We should re-draw popup preview when 1) we need to hide the preview,
  916. // 2) we will show
  917. // the space key preview and 3) pointer moves off the space key to other
  918. // letter key, we
  919. // should hide the preview of the previous key.
  920. final boolean hidePreviewOrShowSpaceKeyPreview = (tracker == null)
  921. || tracker.isSpaceKey(keyIndex) || tracker.isSpaceKey(oldKeyIndex);
  922. // If key changed and preview is on or the key is space (language switch
  923. // is enabled)
  924. if (oldKeyIndex != keyIndex
  925. && (mShowPreview
  926. || (hidePreviewOrShowSpaceKeyPreview && isLanguageSwitchEnabled))) {
  927. if (keyIndex == NOT_A_KEY) {
  928. mHandler.cancelPopupPreview();
  929. mHandler.dismissPreview(mDelayAfterPreview);
  930. } else if (tracker != null) {
  931. mHandler.popupPreview(mDelayBeforePreview, keyIndex, tracker);
  932. }
  933. }
  934. }
  935. private void showKey(final int keyIndex, PointerTracker tracker) {
  936. Key key = tracker.getKey(keyIndex);
  937. if (key == null)
  938. return;
  939. // Should not draw hint icon in key preview
  940. if (key.icon != null && !shouldDrawLabelAndIcon(key)) {
  941. mPreviewText.setCompoundDrawables(null, null, null,
  942. key.iconPreview != null ? key.iconPreview : key.icon);
  943. mPreviewText.setText(null);
  944. } else {
  945. mPreviewText.setCompoundDrawables(null, null, null, null);
  946. mPreviewText.setText(adjustCase(tracker.getPreviewText(key)));
  947. if (key.label.length() > 1 && key.codes.length < 2) {
  948. mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
  949. mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
  950. } else {
  951. mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
  952. mPreviewText.setTypeface(mKeyTextStyle);
  953. }
  954. }
  955. mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
  956. MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
  957. int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
  958. + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
  959. final int popupHeight = mPreviewHeight;
  960. LayoutParams lp = mPreviewText.getLayoutParams();
  961. if (lp != null) {
  962. lp.width = popupWidth;
  963. lp.height = popupHeight;
  964. }
  965. int popupPreviewX = key.x - (popupWidth - key.width) / 2;
  966. int popupPreviewY = key.y - popupHeight + mPreviewOffset;
  967. mHandler.cancelDismissPreview();
  968. if (mOffsetInWindow == null) {
  969. mOffsetInWindow = new int[2];
  970. getLocationInWindow(mOffsetInWindow);
  971. mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
  972. mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
  973. int[] windowLocation = new int[2];
  974. getLocationOnScreen(windowLocation);
  975. mWindowY = windowLocation[1];
  976. }
  977. // Set the preview background state
  978. mPreviewText.getBackground().setState(
  979. key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
  980. popupPreviewX += mOffsetInWindow[0];
  981. popupPreviewY += mOffsetInWindow[1];
  982. // If the popup cannot be shown above the key, put it on the side
  983. if (popupPreviewY + mWindowY < 0) {
  984. // If the key you're pressing is on the left side of the keyboard,
  985. // show the popup on
  986. // the right, offset by enough to see at least one key to the
  987. // left/right.
  988. if (key.x + key.width <= getWidth() / 2) {
  989. popupPreviewX += (int) (key.width * 2.5);
  990. } else {
  991. popupPreviewX -= (int) (key.width * 2.5);
  992. }
  993. popupPreviewY += popupHeight;
  994. }
  995. if (mPreviewPopup.isShowing()) {
  996. mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight);
  997. } else {
  998. mPreviewPopup.setWidth(popupWidth);
  999. mPreviewPopup.setHeight(popupHeight);
  1000. mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY,
  1001. popupPreviewX, popupPreviewY);
  1002. }
  1003. // Record popup preview position to display mini-keyboard later at the
  1004. // same positon
  1005. mPopupPreviewDisplayedY = popupPreviewY;
  1006. mPreviewText.setVisibility(VISIBLE);
  1007. }
  1008. /**
  1009. * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
  1010. * because the keyboard renders the keys to an off-screen buffer and an invalidate() only draws
  1011. * the cached buffer.
  1012. *
  1013. * @see #invalidateKey(Key)
  1014. */
  1015. public void invalidateAllKeys() {
  1016. mDirtyRect.union(0, 0, getWidth(), getHeight());
  1017. mDrawPending = true;
  1018. invalidate();
  1019. }
  1020. /**
  1021. * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only one
  1022. * key is changing it's content. Any changes that affect the position or size of the key may not
  1023. * be honored.
  1024. *
  1025. * @param key key in the attached {@link Keyboard}.
  1026. * @see #invalidateAllKeys
  1027. */
  1028. @Override
  1029. public void invalidateKey(Key key) {
  1030. if (key == null)
  1031. return;
  1032. mInvalidatedKey = key;
  1033. // TODO we should clean up this and record key's region to use in
  1034. // onBufferDraw.
  1035. mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
  1036. key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
  1037. onBufferDraw();
  1038. invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),
  1039. key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
  1040. }
  1041. private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
  1042. // Check if we have a popup layout specified first.
  1043. if (mPopupLayout == 0) {
  1044. return false;
  1045. }
  1046. Key popupKey = tracker.getKey(keyIndex);
  1047. if (popupKey == null)
  1048. return false;
  1049. boolean result = onLongPress(popupKey);
  1050. if (result) {
  1051. dismissKeyPreview();
  1052. mMiniKeyboardTrackerId = tracker.mPointerId;
  1053. // Mark this tracker "already processed" and remove it from the
  1054. // pointer queue
  1055. tracker.setAlreadyProcessed();
  1056. mPointerQueue.remove(tracker);
  1057. }
  1058. return result;
  1059. }
  1060. private View inflateMiniKeyboardContainer(Key popupKey) {
  1061. int popupKeyboardId = popupKey.popupResId;
  1062. LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
  1063. Context.LAYOUT_INFLATER_SERVICE);
  1064. View container = inflater.inflate(mPopupLayout, null);
  1065. if (container == null)
  1066. throw new NullPointerException();
  1067. LatinKeyboardBaseView miniKeyboard =
  1068. (LatinKeyboardBaseView) container.findViewById(R.id.LatinKeyboardBaseView);
  1069. miniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
  1070. @Override
  1071. public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
  1072. mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y);
  1073. dismissPopupKeyboard();
  1074. }
  1075. @Override
  1076. public void onText(CharSequence text) {
  1077. mKeyboardActionListener.onText(text);
  1078. dismissPopupKeyboard();
  1079. }
  1080. @Override
  1081. public void onCancel() {
  1082. dismissPopupKeyboard();
  1083. }
  1084. @Override
  1085. public boolean swipeLeft(int pointerCount) {
  1086. return false;
  1087. }
  1088. @Override
  1089. public boolean swipeRight(int pointerCount) {
  1090. return false;
  1091. }
  1092. @Override
  1093. public boolean swipeUp(int pointerCount) {
  1094. return false;
  1095. }
  1096. @Override
  1097. public boolean swipeDown(int pointerCount) {
  1098. return false;
  1099. }
  1100. @Override
  1101. public boolean singleTap(int pointerCount) {
  1102. return false;
  1103. }
  1104. @Override
  1105. public boolean doubleTap(int pointerCount) {
  1106. return false;
  1107. }
  1108. @Override
  1109. public boolean longPress(int pointerCount) {
  1110. return false;
  1111. }
  1112. @Override
  1113. public void exploreKeyboardArea() {
  1114. }
  1115. @Override
  1116. public void enteredSegment(int segment) {
  1117. }
  1118. @Override
  1119. public void selectedSegment(int segment) {
  1120. }
  1121. @Override
  1122. public void leftKeyboardArea() {
  1123. }
  1124. @Override
  1125. public void enteredKeyboardArea() {
  1126. }
  1127. @Override
  1128. public void upOutsideKeyboardArea() {
  1129. }
  1130. @Override
  1131. public void onPress(Key key) {
  1132. mKeyboardActionListener.onPress(key);
  1133. }
  1134. @Override
  1135. public void onLeaving(Key key) {
  1136. mKeyboardActionListener.onLeaving(key);
  1137. }
  1138. @Override
  1139. public void onRelease(Key key) {
  1140. mKeyboardActionListener.onRelease(key);
  1141. }
  1142. });
  1143. // Override default ProximityKeyDetector.
  1144. miniKeyboard.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance);
  1145. // Remove gesture detector on mini-keyboard
  1146. miniKeyboard.mGestureDetector = null;
  1147. Keyboard keyboard;
  1148. if (popupKey.popupCharacters != null) {
  1149. keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.popupCharacters,
  1150. -1, getPaddingLeft() + getPaddingRight());
  1151. } else {
  1152. keyboard = new Keyboard(getContext(), popupKeyboardId);
  1153. }
  1154. miniKeyboard.setKeyboard(keyboard);
  1155. miniKeyboard.setPopupParent(this);
  1156. container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
  1157. MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
  1158. return container;
  1159. }
  1160. private static boolean isOneRowKeys(List<Key> keys) {
  1161. if (keys.size() == 0)
  1162. return false;
  1163. final int edgeFlags = keys.get(0).edgeFlags;
  1164. // HACK: The first key of mini keyboard which was inflated from xml and
  1165. // has multiple rows,
  1166. // does not have both top and bottom edge flags on at the same time. On
  1167. // the other hand,
  1168. // the first key of mini keyboard that was created with popupCharacters
  1169. // must have both top
  1170. // and bottom edge flags on.
  1171. // When you want to use one row mini-keyboard from xml file, make sure
  1172. // that the row has
  1173. // both top and bottom edge flags set.
  1174. return (edgeFlags & Keyboard.EDGE_TOP) != 0 && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0;
  1175. }
  1176. /**
  1177. * Called when a key is long pressed. By default this will open any popup keyboard associated
  1178. * with this key through the attributes popupLayout and popupCharacters.
  1179. *
  1180. * @param popupKey the key that was long pressed
  1181. * @return true if the long press is handled, false otherwise. Subclasses should call the method
  1182. * on the base class if the subclass doesn't wish to handle the call.
  1183. */
  1184. protected boolean onLongPress(Key popupKey) {
  1185. // TODO if popupKey.popupCharacters has only one letter, send it as key
  1186. // without opening
  1187. // mini keyboard.
  1188. if (popupKey.popupResId == 0)
  1189. return false;
  1190. View container = mMiniKeyboardCache.get(popupKey);
  1191. if (container == null) {
  1192. container = inflateMiniKeyboardContainer(popupKey);
  1193. mMiniKeyboardCache.put(popupKey, container);
  1194. }
  1195. mMiniKeyboard = (LatinKeyboardBaseView) container.findViewById(R.id.LatinKeyboardBaseView);
  1196. if (mWindowOffset == null) {
  1197. mWindowOffset = new int[2];
  1198. getLocationInWindow(mWindowOffset);
  1199. }
  1200. // Get width of a key in the mini popup keyboard = "miniKeyWidth".
  1201. // On the other hand, "popupKey.width" is width of the pressed key on
  1202. // the main keyboard.
  1203. // We adjust the position of mini popup keyboard with the edge key in
  1204. // it:
  1205. // a) When we have the leftmost key in popup keyboard directly above the
  1206. // pressed key
  1207. // Right edges of both keys should be aligned for consistent default
  1208. // selection
  1209. // b) When we have the rightmost key in popup keyboard directly above
  1210. // the pressed key
  1211. // Left edges of both keys should be aligned for consistent default
  1212. // selection
  1213. final List<Key> miniKeys = mMiniKeyboard.getKeyboard().getKeys();
  1214. final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).width : 0;
  1215. // HACK: Have the leftmost number in the popup characters right above
  1216. // the key
  1217. boolean isNumberAtLeftmost =
  1218. hasMultiplePopupChars(popupKey) && isNumberAtLeftmostPopupChar(popupKey);
  1219. int popupX = popupKey.x + mWindowOffset[0];
  1220. popupX += getPaddingLeft();
  1221. if (isNumberAtLeftmost) {
  1222. popupX += popupKey.width - miniKeyWidth; // adjustment for a)
  1223. // described above
  1224. popupX -= container.getPaddingLeft();
  1225. } else {
  1226. popupX += miniKeyWidth; // adjustment for b) described above
  1227. popupX -= container.getMeasuredWidth();
  1228. popupX += container.getPaddingRight();
  1229. }
  1230. int popupY = popupKey.y + mWindowOffset[1];
  1231. popupY += getPaddingTop();
  1232. popupY -= container.getMeasuredHeight();
  1233. popupY += container.getPaddingBottom();
  1234. final int x = popupX;
  1235. final int y = mShowPreview && isOneRowKeys(miniKeys) ? mPopupPreviewDisplayedY : popupY;
  1236. int adjustedX = x;
  1237. if (x < 0) {
  1238. adjustedX = 0;
  1239. } else if (x > (getMeasuredWidth() - container.getMeasuredWidth())) {
  1240. adjustedX = getMeasuredWidth() - container.getMeasuredWidth();
  1241. }
  1242. mMiniKeyboardOriginX = adjustedX + container.getPaddingLeft() - mWindowOffset[0];
  1243. mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1];
  1244. mMiniKeyboard.setPopupOffset(adjustedX, y);
  1245. mMiniKeyboard.setShifted(isShifted());
  1246. // Mini keyboard needs no pop-up key preview displayed.
  1247. mMiniKeyboard.setPreviewEnabled(false);
  1248. mMiniKeyboardPopup.setContentView(container);
  1249. mMiniKeyboardPopup.setWidth(container.getMeasuredWidth());
  1250. mMiniKeyboardPopup.setHeight(container.getMeasuredHeight());
  1251. mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
  1252. // Inject down event on the key to mini keyboard.
  1253. long eventTime = SystemClock.uptimeMillis();
  1254. mMiniKeyboardPopupTime = eventTime;
  1255. MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.x
  1256. + popupKey.width / 2, popupKey.y + popupKey.height / 2, eventTime);
  1257. mMiniKeyboard.onTouchEvent(downEvent);
  1258. downEvent.recycle();
  1259. invalidateAllKeys();
  1260. return true;
  1261. }
  1262. private static boolean hasMultiplePopupChars(Key key) {
  1263. if (key.popupCharacters != null && key.popupCharacters.length() > 1) {
  1264. return true;
  1265. }
  1266. return false;
  1267. }
  1268. private boolean shouldDrawIconFully(Key key) {
  1269. return isNumberAtEdgeOfPopupChars(key) || isLatinF1Key(key)
  1270. || LatinKeyboard.hasPuncOrSmileysPopup(key);
  1271. }
  1272. private boolean shouldDrawLabelAndIcon(Key key) {
  1273. return isNumberAtEdgeOfPopupChars(key) || isNonMicLatinF1Key(key)
  1274. || LatinKeyboard.hasPuncOrSmileysPopup(key);
  1275. }
  1276. private boolean isLatinF1Key(Key key) {
  1277. return (mKeyboard instanceof LatinKeyboard) && ((LatinKeyboard) mKeyboard).isF1Key(key);
  1278. }
  1279. private boolean isNonMicLatinF1Key(Key key) {
  1280. return isLatinF1Key(key) && key.label != null;
  1281. }
  1282. private static boolean isNumberAtEdgeOfPopupChars(Key key) {
  1283. return isNumberAtLeftmostPopupChar(key) || isNumberAtRightmostPopupChar(key);
  1284. }
  1285. /* package */static boolean isNumberAtLeftmostPopupChar(Key key) {
  1286. if (key.popupCharacters != null && key.popupCharacters.length() > 0
  1287. && isAsciiDigit(key.popupCharacters.charAt(0))) {
  1288. return true;
  1289. }
  1290. return false;
  1291. }
  1292. /* package */static boolean isNumberAtRightmostPopupChar(Key key) {
  1293. if (key.popupCharacters != null && key.popupCharacters.length() > 0
  1294. && isAsciiDigit(key.popupCharacters.charAt(key.popupCharacters.length() - 1))) {
  1295. return true;
  1296. }
  1297. return false;
  1298. }
  1299. private static boolean isAsciiDigit(char c) {
  1300. return (c < 0x80) && Character.isDigit(c);
  1301. }
  1302. private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
  1303. return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action,
  1304. x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
  1305. }
  1306. private PointerTracker getPointerTracker(final int id) {
  1307. final ArrayList<PointerTracker> pointers = mPointerTrackers;
  1308. final Key[] keys = mKeys;
  1309. final OnKeyboardActionListener listener = mKeyboardActionListener;
  1310. // Create pointer trackers until we can get 'id+1'-th tracker, if
  1311. // needed.
  1312. for (int i = pointers.size(); i <= id; i++) {
  1313. final PointerTracker tracker =
  1314. new PointerTracker(i, mHandler, mKeyDetector, this, getResources());
  1315. if (keys != null)
  1316. tracker.setKeyboard(keys, mKeyHysteresisDistance);
  1317. if (listener != null)
  1318. tracker.setOnKeyboardActionListener(listener);
  1319. pointers.add(tracker);
  1320. }
  1321. return pointers.get(id);
  1322. }
  1323. /**
  1324. * Handles HOVER type events in API 14+.
  1325. *
  1326. * @param e The hover motion event.
  1327. * @return True if the event was handled, false otherwise.
  1328. */
  1329. public boolean onHoverEvent(MotionEvent e) {
  1330. final MotionEvent touchEvent = MotionEventCompatUtils.convertHoverToTouch(e);
  1331. final boolean result = onTouchEvent(touchEvent);
  1332. touchEvent.recycle();
  1333. return result;
  1334. }
  1335. @Override
  1336. public boolean onTouchEvent(MotionEvent me) {
  1337. return onTouchEvent(me, false);
  1338. }
  1339. public boolean onTouchEvent(MotionEvent me, boolean bypassGestureDetector) {
  1340. final int pointerCount = me.getPointerCount();
  1341. final int action = me.getAction() & MotionEvent.ACTION_MASK;
  1342. if (mMiniKeyboard == null && !bypassGestureDetector
  1343. && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
  1344. dismissKeyPreview();
  1345. mHandler.cancelKeyTimers();
  1346. return true;
  1347. }
  1348. if (mMiniKeyboard == null && !bypassGestureDetector
  1349. && mSegmentDetector != null && mSegmentDetector.onTouchEvent(me)) {
  1350. dismissKeyPreview();
  1351. mHandler.cancelKeyTimers();
  1352. return true;
  1353. }
  1354. final long eventTime = me.getEventTime();
  1355. final int index = (me.getAction() & MotionEvent.ACTION_POINTER_ID_MASK)
  1356. >> MotionEvent.ACTION_POINTER_ID_SHIFT;
  1357. final int id = me.getPointerId(index);
  1358. final int x = (int) me.getX(index);
  1359. final int y = (int) me.getY(index);
  1360. // Needs to be called after the gesture detector gets a turn, as it may
  1361. // have
  1362. // displayed the mini keyboard
  1363. if (mMiniKeyboard != null) {
  1364. final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId);
  1365. if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) {
  1366. final int miniKeyboardX = (int) me.getX(miniKeyboardPointerIndex);
  1367. final int miniKeyboardY = (int) me.getY(miniKeyboardPointerIndex);
  1368. MotionEvent translated = generateMiniKeyboardMotionEvent(action,
  1369. miniKeyboardX, miniKeyboardY, eventTime);
  1370. mMiniKeyboard.onTouchEvent(translated);
  1371. translated.recycle();
  1372. }
  1373. return true;
  1374. }
  1375. if (mHandler.isInKeyRepeat()) {
  1376. // It will keep being in the key repeating mode while the key is
  1377. // being pressed.
  1378. if (action == MotionEvent.ACTION_MOVE) {
  1379. return true;
  1380. }
  1381. final PointerTracker tracker = getPointerTracker(id);
  1382. // Key repeating timer will be canceled if 2 or more keys are in
  1383. // action, and current
  1384. // event (UP or DOWN) is non-modifier key.
  1385. if (pointerCount > 1 && !tracker.isModifier()) {
  1386. mHandler.cancelKeyRepeatTimer();
  1387. }
  1388. // Up event will pass through.
  1389. }
  1390. // TODO: cleanup this code into a multi-touch to single-touch event
  1391. // converter class?
  1392. // Translate mutli-touch event to single-touch events on the device that
  1393. // has no distinct
  1394. // multi-touch panel.
  1395. if (!mHasDistinctMultitouch || mIsAccessibilityEnabled) {
  1396. // Use only main (id=0) pointer tracker.
  1397. PointerTracker tracker = getPointerTracker(0);
  1398. int oldPointerCount = mOldPointerCount;
  1399. if (pointerCount == 1 && oldPointerCount == 2) {
  1400. // Multi-touch to single touch transition.
  1401. // Send a down event for the latest pointer.
  1402. tracker.onDownEvent(x, y, eventTime);
  1403. } else if (pointerCount == 2 && oldPointerCount == 1) {
  1404. // Single-touch to multi-touch transition.
  1405. // Send an up event for the last pointer.
  1406. tracker.onUpEvent(tracker.getLastX(), tracker.getLastY(), eventTime);
  1407. } else if (pointerCount == 1 && oldPointerCount == 1) {
  1408. // If accessibility is enabled and we move off-keyboard, cancel
  1409. // the key press.
  1410. if (!pointInView(x, y, mTouchSlop)) {
  1411. if (mInKeyboardArea) {
  1412. mInKeyboardArea = false;
  1413. tracker.onCancelEvent(x, y, eventTime);
  1414. mKeyboardActionListener.leftKeyboardArea();
  1415. } else if (action == MotionEvent.ACTION_UP) {
  1416. mKeyboardActionListener.upOutsideKeyboardArea();
  1417. }
  1418. } else {
  1419. if (mInKeyboardArea) {
  1420. tracker.onTouchEvent(action, x, y, eventTime);
  1421. mKeyboardActionListener.exploreKeyboardArea();
  1422. } else {
  1423. mInKeyboardArea = true;
  1424. tracker.onDownEvent(x, y, eventTime);
  1425. // Only enter if we're moving back into the keyboard
  1426. // area.
  1427. if (action == MotionEvent.ACTION_MOVE) {
  1428. mKeyboardActionListener.enteredKeyboardArea();
  1429. }
  1430. }
  1431. }
  1432. } else {
  1433. Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
  1434. + " (old " + oldPointerCount + ")");
  1435. }
  1436. mOldPointerCount = pointerCount;
  1437. return true;
  1438. }
  1439. if (action == MotionEvent.ACTION_MOVE) {
  1440. for (int i = 0; i < pointerCount; i++) {
  1441. PointerTracker tracker = getPointerTracker(me.getPointerId(i));
  1442. tracker.onMoveEvent((int) me.getX(i), (int) me.getY(i), eventTime);
  1443. }
  1444. } else {
  1445. PointerTracker tracker = getPointerTracker(id);
  1446. switch (action) {
  1447. case MotionEvent.ACTION_DOWN:
  1448. case MotionEvent.ACTION_POINTER_DOWN:
  1449. onDownEvent(tracker, x, y, eventTime);
  1450. break;
  1451. case MotionEvent.ACTION_UP:
  1452. case MotionEvent.ACTION_POINTER_UP:
  1453. onUpEvent(tracker, x, y, eventTime);
  1454. break;
  1455. case MotionEvent.ACTION_CANCEL:
  1456. onCancelEvent(tracker, x, y, eventTime);
  1457. break;
  1458. }
  1459. }
  1460. return true;
  1461. }
  1462. /**
  1463. * Utility method to determine whether the given point, in local coordinates, is inside the
  1464. * view, where the area of the view is expanded by the slop factor. This method is called while
  1465. * processing touch-move events to determine if the event is still within the view.
  1466. */
  1467. private boolean pointInView(float localX, float localY, float slop) {
  1468. return localX >= -slop && localY >= -slop && localX < ((getRight() - getLeft()) + slop)
  1469. && localY < ((getBottom() - getTop()) + slop);
  1470. }
  1471. private void onDownEvent(PointerTracker tracker, int x, int y, long eventTime) {
  1472. if (tracker.isOnModifierKey(x, y)) {
  1473. // Before processing a down event of modifier key, all pointers
  1474. // already being tracked
  1475. // should be released.
  1476. mPointerQueue.releaseAllPointersExcept(null, eventTime);
  1477. }
  1478. tracker.onDownEvent(x, y, eventTime);
  1479. mPointerQueue.add(tracker);
  1480. }
  1481. private void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) {
  1482. if (tracker.isModifier()) {
  1483. // Before processing an up event of modifier key, all pointers
  1484. // already being tracked
  1485. // should be released.
  1486. mPointerQueue.releaseAllPointersExcept(tracker, eventTime);
  1487. } else {
  1488. int index = mPointerQueue.lastIndexOf(tracker);
  1489. if (index >= 0) {
  1490. mPointerQueue.releaseAllPointersOlderThan(tracker, eventTime);
  1491. } else {
  1492. Log.w(TAG, "onUpEvent: corresponding down event not found for pointer "
  1493. + tracker.mPointerId);
  1494. }
  1495. }
  1496. tracker.onUpEvent(x, y, eventTime);
  1497. mPointerQueue.remove(tracker);
  1498. }
  1499. private void onCancelEvent(PointerTracker tracker, int x, int y, long eventTime) {
  1500. tracker.onCancelEvent(x, y, eventTime);
  1501. mPointerQueue.remove(tracker);
  1502. }
  1503. public void closing() {
  1504. mPreviewPopup.dismiss();
  1505. mHandler.cancelAllMessages();
  1506. dismissPopupKeyboard();
  1507. mBuffer = null;
  1508. mCanvas = null;
  1509. mMiniKeyboardCache.clear();
  1510. }
  1511. @Override
  1512. public void onDetachedFromWindow() {
  1513. super.onDetachedFromWindow();
  1514. closing();
  1515. }
  1516. private void dismissPopupKeyboard() {
  1517. if (mMiniKeyboardPopup.isShowing()) {
  1518. mMiniKeyboardPopup.dismiss();
  1519. mMiniKeyboard = null;
  1520. mMiniKeyboardOriginX = 0;
  1521. mMiniKeyboardOriginY = 0;
  1522. invalidateAllKeys();
  1523. }
  1524. }
  1525. public boolean handleBack() {
  1526. if (mMiniKeyboardPopup.isShowing()) {
  1527. dismissPopupKeyboard();
  1528. return true;
  1529. }
  1530. return false;
  1531. }
  1532. private final SegmentDetector.SegmentListener segmentListener = new SegmentListener() {
  1533. @Override
  1534. public void onLeftKeyboard() {
  1535. mKeyboardActionListener.leftKeyboardArea();
  1536. }
  1537. @Override
  1538. public void onEnteredKeyboard() {
  1539. mKeyboardActionListener.enteredKeyboardArea();
  1540. }
  1541. @Override
  1542. public void onSelectedSegment(int segmentId) {
  1543. mKeyboardActionListener.selectedSegment(segmentId);
  1544. }
  1545. @Override
  1546. public void onEnteredSegment(int segmentId) {
  1547. mKeyboardActionListener.enteredSegment(segmentId);
  1548. }
  1549. };
  1550. private final SimpleMultitouchGestureListener multitouchListener = new SimpleMultitouchGestureListener() {
  1551. @Override
  1552. public boolean onTap(MotionEvent ev) {
  1553. boolean handled = false;
  1554. if (mKeyboardActionListener.singleTap(ev.getPointerCount())) {
  1555. handled = true;
  1556. } else {
  1557. // Only the _UP is necessary for typing, the _DOWN generates a
  1558. // click noise.
  1559. MotionEvent up = MotionEvent.obtain(ev);
  1560. up.setAction(KeyEvent.ACTION_UP);
  1561. handled = onTouchEvent(up, true);
  1562. }
  1563. return handled;
  1564. }
  1565. @Override
  1566. public boolean onSimpleTap(int pointerCount, float centroidX, float centroidY) {
  1567. return false;
  1568. }
  1569. @Override
  1570. public boolean onSimpleDoubleTap(int pointerCount, float centroidX, float centroidY) {
  1571. return mKeyboardActionListener.doubleTap(pointerCount);
  1572. }
  1573. @Override
  1574. public boolean onDown(MotionEvent e) {
  1575. return onTouchEvent(e, true);
  1576. }
  1577. @Override
  1578. public boolean onSimpleDown(int pointerCount, float centroidX, float centroidY) {
  1579. return false;
  1580. }
  1581. @Override
  1582. public boolean onSimpleMove(int pointerCount, float centroidX, float centroidY) {
  1583. return false;
  1584. }
  1585. @Override
  1586. public boolean onSimpleFlick(int pointerCount, int direction) {
  1587. switch (direction) {
  1588. case FLICK_UP:
  1589. return mKeyboardActionListener.swipeUp(pointerCount);
  1590. case FLICK_DOWN:
  1591. return mKeyboardActionListener.swipeDown(pointerCount);
  1592. case FLICK_LEFT:
  1593. return mKeyboardActionListener.swipeLeft(pointerCount);
  1594. case FLICK_RIGHT:
  1595. return mKeyboardActionListener.swipeRight(pointerCount);
  1596. default:
  1597. return false;
  1598. }
  1599. }
  1600. @Override
  1601. public boolean onSimpleLongPress(int pointerCount, float centroidX, float centroidY) {
  1602. return mKeyboardActionListener.longPress(pointerCount);
  1603. }
  1604. };
  1605. }