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

http://eyes-free.googlecode.com/ · Java · 721 lines · 580 code · 70 blank · 71 comment · 148 complexity · 095b28f15d3ac64558384b339db0d748 MD5 · raw file

  1. /*
  2. * Copyright (C) 2008 The Android Open Source Project
  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. package com.googlecode.eyesfree.inputmethod.latin;
  17. import android.content.Context;
  18. import android.content.res.Resources;
  19. import android.content.res.XmlResourceParser;
  20. import android.graphics.Bitmap;
  21. import android.graphics.Canvas;
  22. import android.graphics.PorterDuff;
  23. import android.graphics.Rect;
  24. import android.graphics.drawable.BitmapDrawable;
  25. import android.graphics.drawable.Drawable;
  26. import android.inputmethodservice.Keyboard;
  27. import android.util.Log;
  28. import android.view.inputmethod.EditorInfo;
  29. import java.util.List;
  30. import java.util.Locale;
  31. public class LatinKeyboard extends Keyboard {
  32. private static final boolean DEBUG_PREFERRED_LETTER = false;
  33. private static final String TAG = "LatinKeyboard";
  34. private static final int OPACITY_FULLY_OPAQUE = 255;
  35. private static final int SPACE_LED_LENGTH_PERCENT = 80;
  36. private Drawable mShiftLockIcon;
  37. private Drawable mShiftLockPreviewIcon;
  38. private Drawable mOldShiftIcon;
  39. private Drawable mSpaceIcon;
  40. private Drawable mSpaceAutoCompletionIndicator;
  41. private Drawable mMicIcon;
  42. private Drawable mMicPreviewIcon;
  43. private Drawable m123MicIcon;
  44. private Drawable m123MicPreviewIcon;
  45. private Key mShiftKey;
  46. private Key mEnterKey;
  47. private Key mF1Key;
  48. private final Drawable mHintIcon;
  49. private Key mSpaceKey;
  50. private Key m123Key;
  51. private final int NUMBER_HINT_COUNT = 10;
  52. private Key[] mNumberHintKeys = new Key[NUMBER_HINT_COUNT];
  53. private Drawable[] mNumberHintIcons = new Drawable[NUMBER_HINT_COUNT];
  54. private int mSpaceKeyIndex = -1;
  55. private Locale mLocale;
  56. private final Resources mRes;
  57. private int mMode;
  58. // Whether this keyboard has voice icon on it
  59. private boolean mHasVoiceButton;
  60. // Whether voice icon is enabled at all
  61. private boolean mVoiceEnabled;
  62. private final boolean mIsAlphaKeyboard;
  63. private CharSequence m123Label;
  64. private boolean mCurrentlyInSpace;
  65. private int[] mPrefLetterFrequencies;
  66. private int mPrefLetter;
  67. private int mPrefLetterX;
  68. private int mPrefLetterY;
  69. private int mPrefDistance;
  70. // TODO: generalize for any keyboardId
  71. private boolean mIsBlackSym;
  72. // TODO: remove this attribute when either Keyboard.mDefaultVerticalGap or Key.parent becomes
  73. // non-private.
  74. private final int mVerticalGap;
  75. private static final int SHIFT_OFF = 0;
  76. private static final int SHIFT_ON = 1;
  77. private static final int SHIFT_LOCKED = 2;
  78. private int mShiftState = SHIFT_OFF;
  79. private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f;
  80. private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f;
  81. public LatinKeyboard(Context context, int xmlLayoutResId) {
  82. this(context, xmlLayoutResId, 0);
  83. }
  84. public LatinKeyboard(Context context, int xmlLayoutResId, int mode) {
  85. super(context, xmlLayoutResId, mode);
  86. final Resources res = context.getResources();
  87. mMode = mode;
  88. mRes = res;
  89. mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
  90. mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
  91. setDefaultBounds(mShiftLockPreviewIcon);
  92. mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space);
  93. mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
  94. mMicIcon = res.getDrawable(R.drawable.sym_keyboard_mic);
  95. mMicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_mic);
  96. setDefaultBounds(mMicPreviewIcon);
  97. m123MicIcon = res.getDrawable(R.drawable.sym_keyboard_123_mic);
  98. m123MicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_123_mic);
  99. mHintIcon = res.getDrawable(R.drawable.hint_popup);
  100. setDefaultBounds(m123MicPreviewIcon);
  101. mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty
  102. || xmlLayoutResId == R.xml.kbd_qwerty_black;
  103. mSpaceKeyIndex = indexOf(LatinIME.KEYCODE_SPACE);
  104. initializeNumberHintResources(context);
  105. // TODO remove this initialization after cleanup
  106. mVerticalGap = super.getVerticalGap();
  107. }
  108. private void initializeNumberHintResources(Context context) {
  109. final Resources res = context.getResources();
  110. mNumberHintIcons[0] = res.getDrawable(R.drawable.keyboard_hint_0);
  111. mNumberHintIcons[1] = res.getDrawable(R.drawable.keyboard_hint_1);
  112. mNumberHintIcons[2] = res.getDrawable(R.drawable.keyboard_hint_2);
  113. mNumberHintIcons[3] = res.getDrawable(R.drawable.keyboard_hint_3);
  114. mNumberHintIcons[4] = res.getDrawable(R.drawable.keyboard_hint_4);
  115. mNumberHintIcons[5] = res.getDrawable(R.drawable.keyboard_hint_5);
  116. mNumberHintIcons[6] = res.getDrawable(R.drawable.keyboard_hint_6);
  117. mNumberHintIcons[7] = res.getDrawable(R.drawable.keyboard_hint_7);
  118. mNumberHintIcons[8] = res.getDrawable(R.drawable.keyboard_hint_8);
  119. mNumberHintIcons[9] = res.getDrawable(R.drawable.keyboard_hint_9);
  120. }
  121. @Override
  122. protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
  123. XmlResourceParser parser) {
  124. Key key = new LatinKey(res, parent, x, y, parser);
  125. switch (key.codes[0]) {
  126. case LatinIME.KEYCODE_ENTER:
  127. mEnterKey = key;
  128. break;
  129. case LatinKeyboardView.KEYCODE_F1:
  130. mF1Key = key;
  131. break;
  132. case LatinIME.KEYCODE_SPACE:
  133. mSpaceKey = key;
  134. break;
  135. case KEYCODE_MODE_CHANGE:
  136. m123Key = key;
  137. m123Label = key.label;
  138. break;
  139. }
  140. // For number hints on the upper-right corner of key
  141. if (mNumberHintKeys == null) {
  142. // NOTE: This protected method is being called from the base class constructor before
  143. // mNumberHintKeys gets initialized.
  144. mNumberHintKeys = new Key[NUMBER_HINT_COUNT];
  145. }
  146. int hintNumber = -1;
  147. if (LatinKeyboardBaseView.isNumberAtLeftmostPopupChar(key)) {
  148. hintNumber = key.popupCharacters.charAt(0) - '0';
  149. } else if (LatinKeyboardBaseView.isNumberAtRightmostPopupChar(key)) {
  150. hintNumber = key.popupCharacters.charAt(key.popupCharacters.length() - 1) - '0';
  151. }
  152. if (hintNumber >= 0 && hintNumber <= 9) {
  153. mNumberHintKeys[hintNumber] = key;
  154. }
  155. return key;
  156. }
  157. void setImeOptions(Resources res, int mode, int options) {
  158. mMode = mode;
  159. // TODO should clean up this method
  160. if (mEnterKey != null) {
  161. // Reset some of the rarely used attributes.
  162. mEnterKey.popupCharacters = null;
  163. mEnterKey.popupResId = 0;
  164. mEnterKey.text = null;
  165. switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
  166. case EditorInfo.IME_ACTION_GO:
  167. mEnterKey.iconPreview = null;
  168. mEnterKey.icon = null;
  169. mEnterKey.label = res.getText(R.string.label_go_key);
  170. break;
  171. case EditorInfo.IME_ACTION_NEXT:
  172. mEnterKey.iconPreview = null;
  173. mEnterKey.icon = null;
  174. mEnterKey.label = res.getText(R.string.label_next_key);
  175. break;
  176. case EditorInfo.IME_ACTION_DONE:
  177. mEnterKey.iconPreview = null;
  178. mEnterKey.icon = null;
  179. mEnterKey.label = res.getText(R.string.label_done_key);
  180. break;
  181. case EditorInfo.IME_ACTION_SEARCH:
  182. mEnterKey.iconPreview = res.getDrawable(
  183. R.drawable.sym_keyboard_feedback_search);
  184. mEnterKey.icon = res.getDrawable(mIsBlackSym ?
  185. R.drawable.sym_bkeyboard_search : R.drawable.sym_keyboard_search);
  186. mEnterKey.label = null;
  187. break;
  188. case EditorInfo.IME_ACTION_SEND:
  189. mEnterKey.iconPreview = null;
  190. mEnterKey.icon = null;
  191. mEnterKey.label = res.getText(R.string.label_send_key);
  192. break;
  193. default:
  194. if (mode == KeyboardSwitcher.MODE_IM) {
  195. mEnterKey.icon = mHintIcon;
  196. mEnterKey.iconPreview = null;
  197. mEnterKey.label = ":-)";
  198. mEnterKey.text = ":-) ";
  199. mEnterKey.popupResId = R.xml.popup_smileys;
  200. } else {
  201. mEnterKey.iconPreview = res.getDrawable(
  202. R.drawable.sym_keyboard_feedback_return);
  203. mEnterKey.icon = res.getDrawable(mIsBlackSym ?
  204. R.drawable.sym_bkeyboard_return : R.drawable.sym_keyboard_return);
  205. mEnterKey.label = null;
  206. }
  207. break;
  208. }
  209. // Set the initial size of the preview icon
  210. if (mEnterKey.iconPreview != null) {
  211. setDefaultBounds(mEnterKey.iconPreview);
  212. }
  213. }
  214. }
  215. void enableShiftLock() {
  216. int index = getShiftKeyIndex();
  217. if (index >= 0) {
  218. mShiftKey = getKeys().get(index);
  219. if (mShiftKey instanceof LatinKey) {
  220. ((LatinKey)mShiftKey).enableShiftLock();
  221. }
  222. mOldShiftIcon = mShiftKey.icon;
  223. }
  224. }
  225. void setShiftLocked(boolean shiftLocked) {
  226. if (mShiftKey != null) {
  227. if (shiftLocked) {
  228. mShiftKey.on = true;
  229. mShiftKey.icon = mShiftLockIcon;
  230. mShiftState = SHIFT_LOCKED;
  231. } else {
  232. mShiftKey.on = false;
  233. mShiftKey.icon = mShiftLockIcon;
  234. mShiftState = SHIFT_ON;
  235. }
  236. }
  237. }
  238. boolean isShiftLocked() {
  239. return mShiftState == SHIFT_LOCKED;
  240. }
  241. @Override
  242. public boolean setShifted(boolean shiftState) {
  243. boolean shiftChanged = false;
  244. if (mShiftKey != null) {
  245. if (shiftState == false) {
  246. shiftChanged = mShiftState != SHIFT_OFF;
  247. mShiftState = SHIFT_OFF;
  248. mShiftKey.on = false;
  249. mShiftKey.icon = mOldShiftIcon;
  250. } else {
  251. if (mShiftState == SHIFT_OFF) {
  252. shiftChanged = mShiftState == SHIFT_OFF;
  253. mShiftState = SHIFT_ON;
  254. mShiftKey.icon = mShiftLockIcon;
  255. }
  256. }
  257. } else {
  258. return super.setShifted(shiftState);
  259. }
  260. return shiftChanged;
  261. }
  262. @Override
  263. public boolean isShifted() {
  264. if (mShiftKey != null) {
  265. return mShiftState != SHIFT_OFF;
  266. } else {
  267. return super.isShifted();
  268. }
  269. }
  270. /* package */ boolean isAlphaKeyboard() {
  271. return mIsAlphaKeyboard;
  272. }
  273. public void setColorOfSymbolIcons(boolean isAutoCompletion, boolean isBlack) {
  274. mIsBlackSym = isBlack;
  275. if (isBlack) {
  276. mShiftLockIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_shift_locked);
  277. mSpaceIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_space);
  278. mMicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_mic);
  279. m123MicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_123_mic);
  280. } else {
  281. mShiftLockIcon = mRes.getDrawable(R.drawable.sym_keyboard_shift_locked);
  282. mSpaceIcon = mRes.getDrawable(R.drawable.sym_keyboard_space);
  283. mMicIcon = mRes.getDrawable(R.drawable.sym_keyboard_mic);
  284. m123MicIcon = mRes.getDrawable(R.drawable.sym_keyboard_123_mic);
  285. }
  286. updateDynamicKeys();
  287. if (mSpaceKey != null) {
  288. updateSpaceBarForLocale(isAutoCompletion, isBlack);
  289. }
  290. updateNumberHintKeys();
  291. }
  292. private void setDefaultBounds(Drawable drawable) {
  293. drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
  294. }
  295. public void setVoiceMode(boolean hasVoiceButton, boolean hasVoice) {
  296. mHasVoiceButton = hasVoiceButton;
  297. mVoiceEnabled = hasVoice;
  298. updateDynamicKeys();
  299. }
  300. private void updateDynamicKeys() {
  301. update123Key();
  302. updateF1Key();
  303. }
  304. private void update123Key() {
  305. // Update KEYCODE_MODE_CHANGE key only on alphabet mode, not on symbol mode.
  306. if (m123Key != null && mIsAlphaKeyboard) {
  307. if (mVoiceEnabled && !mHasVoiceButton) {
  308. m123Key.icon = m123MicIcon;
  309. m123Key.iconPreview = m123MicPreviewIcon;
  310. m123Key.label = null;
  311. } else {
  312. m123Key.icon = null;
  313. m123Key.iconPreview = null;
  314. m123Key.label = m123Label;
  315. }
  316. }
  317. }
  318. private void updateF1Key() {
  319. // Update KEYCODE_F1 key. Please note that some keyboard layouts have no F1 key.
  320. if (mF1Key == null)
  321. return;
  322. if (mIsAlphaKeyboard) {
  323. if (mMode == KeyboardSwitcher.MODE_URL) {
  324. setNonMicF1Key(mF1Key, "/", R.xml.popup_slash);
  325. } else if (mMode == KeyboardSwitcher.MODE_EMAIL) {
  326. setNonMicF1Key(mF1Key, "@", R.xml.popup_at);
  327. } else {
  328. if (mVoiceEnabled && mHasVoiceButton) {
  329. setMicF1Key(mF1Key);
  330. } else {
  331. setNonMicF1Key(mF1Key, ",", R.xml.popup_comma);
  332. }
  333. }
  334. } else { // Symbols keyboard
  335. if (mVoiceEnabled && mHasVoiceButton) {
  336. setMicF1Key(mF1Key);
  337. } else {
  338. setNonMicF1Key(mF1Key, ",", R.xml.popup_comma);
  339. }
  340. }
  341. }
  342. private void setMicF1Key(Key key) {
  343. // HACK: draw mMicIcon and mHintIcon at the same time
  344. final Drawable micWithSettingsHintDrawable = new BitmapDrawable(mRes,
  345. drawSynthesizedSettingsHintImage(key.width, key.height, mMicIcon, mHintIcon));
  346. key.label = null;
  347. key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE };
  348. key.popupResId = R.xml.popup_mic;
  349. key.icon = micWithSettingsHintDrawable;
  350. key.iconPreview = mMicPreviewIcon;
  351. }
  352. private void setNonMicF1Key(Key key, String label, int popupResId) {
  353. key.label = label;
  354. key.codes = new int[] { label.charAt(0) };
  355. key.popupResId = popupResId;
  356. key.icon = mHintIcon;
  357. key.iconPreview = null;
  358. }
  359. public boolean isF1Key(Key key) {
  360. return key == mF1Key;
  361. }
  362. public static boolean hasPuncOrSmileysPopup(Key key) {
  363. return key.popupResId == R.xml.popup_punctuation || key.popupResId == R.xml.popup_smileys;
  364. }
  365. /**
  366. * @return a key which should be invalidated.
  367. */
  368. public Key onAutoCompletionStateChanged(boolean isAutoCompletion) {
  369. updateSpaceBarForLocale(isAutoCompletion, mIsBlackSym);
  370. return mSpaceKey;
  371. }
  372. private void updateNumberHintKeys() {
  373. for (int i = 0; i < mNumberHintKeys.length; ++i) {
  374. if (mNumberHintKeys[i] != null) {
  375. mNumberHintKeys[i].icon = mNumberHintIcons[i];
  376. }
  377. }
  378. }
  379. public boolean isLanguageSwitchEnabled() {
  380. return mLocale != null;
  381. }
  382. private void updateSpaceBarForLocale(boolean isAutoCompletion, boolean isBlack) {
  383. // If we don't have a space key, stop.
  384. if (mSpaceKey == null)
  385. return;
  386. // If application locales are explicitly selected.
  387. if (mLocale != null) {
  388. mSpaceKey.icon = new BitmapDrawable(mRes,
  389. drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack));
  390. } else {
  391. // sym_keyboard_space_led can be shared with Black and White symbol themes.
  392. if (isAutoCompletion) {
  393. mSpaceKey.icon = new BitmapDrawable(mRes,
  394. drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack));
  395. } else {
  396. mSpaceKey.icon = isBlack ? mRes.getDrawable(R.drawable.sym_bkeyboard_space)
  397. : mRes.getDrawable(R.drawable.sym_keyboard_space);
  398. }
  399. }
  400. }
  401. // Overlay two images: mainIcon and hintIcon.
  402. private Bitmap drawSynthesizedSettingsHintImage(
  403. int width, int height, Drawable mainIcon, Drawable hintIcon) {
  404. if (mainIcon == null || hintIcon == null)
  405. return null;
  406. Rect hintIconPadding = new Rect(0, 0, 0, 0);
  407. hintIcon.getPadding(hintIconPadding);
  408. final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  409. final Canvas canvas = new Canvas(buffer);
  410. canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
  411. // Draw main icon at the center of the key visual
  412. // Assuming the hintIcon shares the same padding with the key's background drawable
  413. final int drawableX = (width + hintIconPadding.left - hintIconPadding.right
  414. - mainIcon.getIntrinsicWidth()) / 2;
  415. final int drawableY = (height + hintIconPadding.top - hintIconPadding.bottom
  416. - mainIcon.getIntrinsicHeight()) / 2;
  417. setDefaultBounds(mainIcon);
  418. canvas.translate(drawableX, drawableY);
  419. mainIcon.draw(canvas);
  420. canvas.translate(-drawableX, -drawableY);
  421. // Draw hint icon fully in the key
  422. hintIcon.setBounds(0, 0, width, height);
  423. hintIcon.draw(canvas);
  424. return buffer;
  425. }
  426. private Bitmap drawSpaceBar(int opacity, boolean isAutoCompletion, boolean isBlack) {
  427. if (mSpaceKey == null)
  428. return null;
  429. final int width = mSpaceKey.width;
  430. final int height = mSpaceIcon.getIntrinsicHeight();
  431. final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  432. final Canvas canvas = new Canvas(buffer);
  433. canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
  434. // Draw the spacebar icon at the bottom
  435. if (isAutoCompletion) {
  436. final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
  437. final int iconHeight = mSpaceAutoCompletionIndicator.getIntrinsicHeight();
  438. int x = (width - iconWidth) / 2;
  439. int y = height - iconHeight;
  440. mSpaceAutoCompletionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight);
  441. mSpaceAutoCompletionIndicator.draw(canvas);
  442. } else {
  443. final int iconWidth = mSpaceIcon.getIntrinsicWidth();
  444. final int iconHeight = mSpaceIcon.getIntrinsicHeight();
  445. int x = (width - iconWidth) / 2;
  446. int y = height - iconHeight;
  447. mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
  448. mSpaceIcon.draw(canvas);
  449. }
  450. return buffer;
  451. }
  452. boolean isCurrentlyInSpace() {
  453. return mCurrentlyInSpace;
  454. }
  455. void setPreferredLetters(int[] frequencies) {
  456. mPrefLetterFrequencies = frequencies;
  457. mPrefLetter = 0;
  458. }
  459. void keyReleased() {
  460. mCurrentlyInSpace = false;
  461. mPrefLetter = 0;
  462. mPrefLetterX = 0;
  463. mPrefLetterY = 0;
  464. mPrefDistance = Integer.MAX_VALUE;
  465. }
  466. /**
  467. * Does the magic of locking the touch gesture into the spacebar when
  468. * switching input languages.
  469. */
  470. boolean isInside(LatinKey key, int x, int y) {
  471. final int code = key.codes[0];
  472. if (code == KEYCODE_SHIFT ||
  473. code == KEYCODE_DELETE) {
  474. y -= key.height / 10;
  475. if (code == KEYCODE_SHIFT) x += key.width / 6;
  476. if (code == KEYCODE_DELETE) x -= key.width / 6;
  477. } else if (mPrefLetterFrequencies != null) {
  478. // New coordinate? Reset
  479. if (mPrefLetterX != x || mPrefLetterY != y) {
  480. mPrefLetter = 0;
  481. mPrefDistance = Integer.MAX_VALUE;
  482. }
  483. // Handle preferred next letter
  484. final int[] pref = mPrefLetterFrequencies;
  485. if (mPrefLetter > 0) {
  486. if (DEBUG_PREFERRED_LETTER) {
  487. if (mPrefLetter == code && !key.isInsideSuper(x, y)) {
  488. Log.d(TAG, "CORRECTED !!!!!!");
  489. }
  490. }
  491. return mPrefLetter == code;
  492. } else {
  493. final boolean inside = key.isInsideSuper(x, y);
  494. int[] nearby = getNearestKeys(x, y);
  495. List<Key> nearbyKeys = getKeys();
  496. if (inside) {
  497. // If it's a preferred letter
  498. if (inPrefList(code, pref)) {
  499. // Check if its frequency is much lower than a nearby key
  500. mPrefLetter = code;
  501. mPrefLetterX = x;
  502. mPrefLetterY = y;
  503. for (int i = 0; i < nearby.length; i++) {
  504. Key k = nearbyKeys.get(nearby[i]);
  505. if (k != key && inPrefList(k.codes[0], pref)) {
  506. final int dist = distanceFrom(k, x, y);
  507. if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) &&
  508. (pref[k.codes[0]] > pref[mPrefLetter] * 3)) {
  509. mPrefLetter = k.codes[0];
  510. mPrefDistance = dist;
  511. if (DEBUG_PREFERRED_LETTER) {
  512. Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!");
  513. }
  514. break;
  515. }
  516. }
  517. }
  518. return mPrefLetter == code;
  519. }
  520. }
  521. // Get the surrounding keys and intersect with the preferred list
  522. // For all in the intersection
  523. // if distance from touch point is within a reasonable distance
  524. // make this the pref letter
  525. // If no pref letter
  526. // return inside;
  527. // else return thiskey == prefletter;
  528. for (int i = 0; i < nearby.length; i++) {
  529. Key k = nearbyKeys.get(nearby[i]);
  530. if (inPrefList(k.codes[0], pref)) {
  531. final int dist = distanceFrom(k, x, y);
  532. if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB)
  533. && dist < mPrefDistance) {
  534. mPrefLetter = k.codes[0];
  535. mPrefLetterX = x;
  536. mPrefLetterY = y;
  537. mPrefDistance = dist;
  538. }
  539. }
  540. }
  541. // Didn't find any
  542. if (mPrefLetter == 0) {
  543. return inside;
  544. } else {
  545. return mPrefLetter == code;
  546. }
  547. }
  548. }
  549. // Lock into the spacebar
  550. if (mCurrentlyInSpace) return false;
  551. return key.isInsideSuper(x, y);
  552. }
  553. private boolean inPrefList(int code, int[] pref) {
  554. if (code < pref.length && code >= 0) return pref[code] > 0;
  555. return false;
  556. }
  557. private int distanceFrom(Key k, int x, int y) {
  558. if (y > k.y && y < k.y + k.height) {
  559. return Math.abs(k.x + k.width / 2 - x);
  560. } else {
  561. return Integer.MAX_VALUE;
  562. }
  563. }
  564. @Override
  565. public int[] getNearestKeys(int x, int y) {
  566. if (mCurrentlyInSpace) {
  567. return new int[] { mSpaceKeyIndex };
  568. } else {
  569. // Avoid dead pixels at edges of the keyboard
  570. return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
  571. Math.max(0, Math.min(y, getHeight() - 1)));
  572. }
  573. }
  574. private int indexOf(int code) {
  575. List<Key> keys = getKeys();
  576. int count = keys.size();
  577. for (int i = 0; i < count; i++) {
  578. if (keys.get(i).codes[0] == code) return i;
  579. }
  580. return -1;
  581. }
  582. // TODO LatinKey could be static class
  583. class LatinKey extends Keyboard.Key {
  584. // functional normal state (with properties)
  585. private final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
  586. android.R.attr.state_single
  587. };
  588. // functional pressed state (with properties)
  589. private final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
  590. android.R.attr.state_single,
  591. android.R.attr.state_pressed
  592. };
  593. private boolean mShiftLockEnabled;
  594. public LatinKey(Resources res, Keyboard.Row parent, int x, int y,
  595. XmlResourceParser parser) {
  596. super(res, parent, x, y, parser);
  597. if (popupCharacters != null && popupCharacters.length() == 0) {
  598. // If there is a keyboard with no keys specified in popupCharacters
  599. popupResId = 0;
  600. }
  601. }
  602. private void enableShiftLock() {
  603. mShiftLockEnabled = true;
  604. }
  605. // sticky is used for shift key. If a key is not sticky and is modifier,
  606. // the key will be treated as functional.
  607. private boolean isFunctionalKey() {
  608. return !sticky && modifier;
  609. }
  610. @Override
  611. public void onReleased(boolean inside) {
  612. if (!mShiftLockEnabled) {
  613. super.onReleased(inside);
  614. } else {
  615. pressed = !pressed;
  616. }
  617. }
  618. /**
  619. * Overriding this method so that we can reduce the target area for certain keys.
  620. */
  621. @Override
  622. public boolean isInside(int x, int y) {
  623. // TODO This should be done by parent.isInside(this, x, y)
  624. // if Key.parent were protected.
  625. boolean result = LatinKeyboard.this.isInside(this, x, y);
  626. return result;
  627. }
  628. boolean isInsideSuper(int x, int y) {
  629. return super.isInside(x, y);
  630. }
  631. @Override
  632. public int[] getCurrentDrawableState() {
  633. if (isFunctionalKey()) {
  634. if (pressed) {
  635. return KEY_STATE_FUNCTIONAL_PRESSED;
  636. } else {
  637. return KEY_STATE_FUNCTIONAL_NORMAL;
  638. }
  639. }
  640. return super.getCurrentDrawableState();
  641. }
  642. @Override
  643. public int squaredDistanceFrom(int x, int y) {
  644. // We should count vertical gap between rows to calculate the center of this Key.
  645. final int verticalGap = LatinKeyboard.this.mVerticalGap;
  646. final int xDist = this.x + width / 2 - x;
  647. final int yDist = this.y + (height + verticalGap) / 2 - y;
  648. return xDist * xDist + yDist * yDist;
  649. }
  650. }
  651. }