PageRenderTime 65ms CodeModel.GetById 19ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 1ms

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