Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2008-2009 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.inputmethod.latin;
     18 
     19 import java.util.List;
     20 import java.util.Locale;
     21 
     22 import android.content.Context;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.content.res.XmlResourceParser;
     26 import android.graphics.Bitmap;
     27 import android.graphics.Canvas;
     28 import android.graphics.ColorFilter;
     29 import android.graphics.Paint;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.PorterDuff;
     32 import android.graphics.Rect;
     33 import android.graphics.Paint.Align;
     34 import android.graphics.drawable.BitmapDrawable;
     35 import android.graphics.drawable.Drawable;
     36 import android.inputmethodservice.Keyboard;
     37 import android.text.TextPaint;
     38 import android.util.Log;
     39 import android.view.ViewConfiguration;
     40 import android.view.inputmethod.EditorInfo;
     41 
     42 public class LatinKeyboard extends Keyboard {
     43 
     44     private static final boolean DEBUG_PREFERRED_LETTER = false;
     45     private static final String TAG = "LatinKeyboard";
     46 
     47     private Drawable mShiftLockIcon;
     48     private Drawable mShiftLockPreviewIcon;
     49     private Drawable mOldShiftIcon;
     50     private Drawable mOldShiftPreviewIcon;
     51     private Drawable mSpaceIcon;
     52     private Drawable mSpacePreviewIcon;
     53     private Drawable mMicIcon;
     54     private Drawable mMicPreviewIcon;
     55     private Drawable m123MicIcon;
     56     private Drawable m123MicPreviewIcon;
     57     private Drawable mButtonArrowLeftIcon;
     58     private Drawable mButtonArrowRightIcon;
     59     private Key mShiftKey;
     60     private Key mEnterKey;
     61     private Key mF1Key;
     62     private Key mSpaceKey;
     63     private Key m123Key;
     64     private int mSpaceKeyIndex = -1;
     65     private int mSpaceDragStartX;
     66     private int mSpaceDragLastDiff;
     67     /* package */ Locale mLocale;
     68     private LanguageSwitcher mLanguageSwitcher;
     69     private Resources mRes;
     70     private Context mContext;
     71     private int mMode;
     72     // Whether this keyboard has voice icon on it
     73     private boolean mHasVoiceButton;
     74     // Whether voice icon is enabled at all
     75     private boolean mVoiceEnabled;
     76     private boolean mIsAlphaKeyboard;
     77     private CharSequence m123Label;
     78     private boolean mCurrentlyInSpace;
     79     private SlidingLocaleDrawable mSlidingLocaleIcon;
     80     private Rect mBounds = new Rect();
     81     private int[] mPrefLetterFrequencies;
     82     private boolean mPreemptiveCorrection;
     83     private int mPrefLetter;
     84     private int mPrefLetterX;
     85     private int mPrefLetterY;
     86     private int mPrefDistance;
     87 
     88     private int mExtensionResId;
     89 
     90     private static final int SHIFT_OFF = 0;
     91     private static final int SHIFT_ON = 1;
     92     private static final int SHIFT_LOCKED = 2;
     93 
     94     private int mShiftState = SHIFT_OFF;
     95 
     96     private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f;
     97     private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f;
     98     private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f;
     99 
    100     static int sSpacebarVerticalCorrection;
    101 
    102     public LatinKeyboard(Context context, int xmlLayoutResId) {
    103         this(context, xmlLayoutResId, 0);
    104     }
    105 
    106     public LatinKeyboard(Context context, int xmlLayoutResId, int mode) {
    107         super(context, xmlLayoutResId, mode);
    108         final Resources res = context.getResources();
    109         mContext = context;
    110         mMode = mode;
    111         mRes = res;
    112         mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
    113         mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
    114         mShiftLockPreviewIcon.setBounds(0, 0,
    115                 mShiftLockPreviewIcon.getIntrinsicWidth(),
    116                 mShiftLockPreviewIcon.getIntrinsicHeight());
    117         mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space);
    118         mSpacePreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_space);
    119         mMicIcon = res.getDrawable(R.drawable.sym_keyboard_mic);
    120         mMicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_mic);
    121         setDefaultBounds(mMicPreviewIcon);
    122         mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
    123         mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
    124         m123MicIcon = res.getDrawable(R.drawable.sym_keyboard_123_mic);
    125         m123MicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_123_mic);
    126         setDefaultBounds(m123MicPreviewIcon);
    127         sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
    128                 R.dimen.spacebar_vertical_correction);
    129         mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty;
    130         mSpaceKeyIndex = indexOf((int) ' ');
    131     }
    132 
    133     public LatinKeyboard(Context context, int layoutTemplateResId,
    134             CharSequence characters, int columns, int horizontalPadding) {
    135         super(context, layoutTemplateResId, characters, columns, horizontalPadding);
    136     }
    137 
    138     @Override
    139     protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
    140             XmlResourceParser parser) {
    141         Key key = new LatinKey(res, parent, x, y, parser);
    142         switch (key.codes[0]) {
    143         case 10:
    144             mEnterKey = key;
    145             break;
    146         case LatinKeyboardView.KEYCODE_F1:
    147             mF1Key = key;
    148             break;
    149         case 32:
    150             mSpaceKey = key;
    151             break;
    152         case KEYCODE_MODE_CHANGE:
    153             m123Key = key;
    154             m123Label = key.label;
    155             break;
    156         }
    157         return key;
    158     }
    159 
    160     void setImeOptions(Resources res, int mode, int options) {
    161         if (mEnterKey != null) {
    162             // Reset some of the rarely used attributes.
    163             mEnterKey.popupCharacters = null;
    164             mEnterKey.popupResId = 0;
    165             mEnterKey.text = null;
    166             switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
    167                 case EditorInfo.IME_ACTION_GO:
    168                     mEnterKey.iconPreview = null;
    169                     mEnterKey.icon = null;
    170                     mEnterKey.label = res.getText(R.string.label_go_key);
    171                     break;
    172                 case EditorInfo.IME_ACTION_NEXT:
    173                     mEnterKey.iconPreview = null;
    174                     mEnterKey.icon = null;
    175                     mEnterKey.label = res.getText(R.string.label_next_key);
    176                     break;
    177                 case EditorInfo.IME_ACTION_DONE:
    178                     mEnterKey.iconPreview = null;
    179                     mEnterKey.icon = null;
    180                     mEnterKey.label = res.getText(R.string.label_done_key);
    181                     break;
    182                 case EditorInfo.IME_ACTION_SEARCH:
    183                     mEnterKey.iconPreview = res.getDrawable(
    184                             R.drawable.sym_keyboard_feedback_search);
    185                     mEnterKey.icon = res.getDrawable(
    186                             R.drawable.sym_keyboard_search);
    187                     mEnterKey.label = null;
    188                     break;
    189                 case EditorInfo.IME_ACTION_SEND:
    190                     mEnterKey.iconPreview = null;
    191                     mEnterKey.icon = null;
    192                     mEnterKey.label = res.getText(R.string.label_send_key);
    193                     break;
    194                 default:
    195                     if (mode == KeyboardSwitcher.MODE_IM) {
    196                         mEnterKey.icon = null;
    197                         mEnterKey.iconPreview = null;
    198                         mEnterKey.label = ":-)";
    199                         mEnterKey.text = ":-) ";
    200                         mEnterKey.popupResId = R.xml.popup_smileys;
    201                     } else {
    202                         mEnterKey.iconPreview = res.getDrawable(
    203                                 R.drawable.sym_keyboard_feedback_return);
    204                         mEnterKey.icon = res.getDrawable(
    205                                 R.drawable.sym_keyboard_return);
    206                         mEnterKey.label = null;
    207                     }
    208                     break;
    209             }
    210             // Set the initial size of the preview icon
    211             if (mEnterKey.iconPreview != null) {
    212                 mEnterKey.iconPreview.setBounds(0, 0,
    213                         mEnterKey.iconPreview.getIntrinsicWidth(),
    214                         mEnterKey.iconPreview.getIntrinsicHeight());
    215             }
    216         }
    217     }
    218 
    219     void enableShiftLock() {
    220         int index = getShiftKeyIndex();
    221         if (index >= 0) {
    222             mShiftKey = getKeys().get(index);
    223             if (mShiftKey instanceof LatinKey) {
    224                 ((LatinKey)mShiftKey).enableShiftLock();
    225             }
    226             mOldShiftIcon = mShiftKey.icon;
    227             mOldShiftPreviewIcon = mShiftKey.iconPreview;
    228         }
    229     }
    230 
    231     void setShiftLocked(boolean shiftLocked) {
    232         if (mShiftKey != null) {
    233             if (shiftLocked) {
    234                 mShiftKey.on = true;
    235                 mShiftKey.icon = mShiftLockIcon;
    236                 mShiftState = SHIFT_LOCKED;
    237             } else {
    238                 mShiftKey.on = false;
    239                 mShiftKey.icon = mShiftLockIcon;
    240                 mShiftState = SHIFT_ON;
    241             }
    242         }
    243     }
    244 
    245     boolean isShiftLocked() {
    246         return mShiftState == SHIFT_LOCKED;
    247     }
    248 
    249     @Override
    250     public boolean setShifted(boolean shiftState) {
    251         boolean shiftChanged = false;
    252         if (mShiftKey != null) {
    253             if (shiftState == false) {
    254                 shiftChanged = mShiftState != SHIFT_OFF;
    255                 mShiftState = SHIFT_OFF;
    256                 mShiftKey.on = false;
    257                 mShiftKey.icon = mOldShiftIcon;
    258             } else {
    259                 if (mShiftState == SHIFT_OFF) {
    260                     shiftChanged = mShiftState == SHIFT_OFF;
    261                     mShiftState = SHIFT_ON;
    262                     mShiftKey.icon = mShiftLockIcon;
    263                 }
    264             }
    265         } else {
    266             return super.setShifted(shiftState);
    267         }
    268         return shiftChanged;
    269     }
    270 
    271     @Override
    272     public boolean isShifted() {
    273         if (mShiftKey != null) {
    274             return mShiftState != SHIFT_OFF;
    275         } else {
    276             return super.isShifted();
    277         }
    278     }
    279 
    280     public void setExtension(int resId) {
    281         mExtensionResId = resId;
    282     }
    283 
    284     public int getExtension() {
    285         return mExtensionResId;
    286     }
    287 
    288     private void setDefaultBounds(Drawable drawable) {
    289         drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    290     }
    291 
    292     public void setVoiceMode(boolean hasVoiceButton, boolean hasVoice) {
    293         mHasVoiceButton = hasVoiceButton;
    294         mVoiceEnabled = hasVoice;
    295         updateF1Key();
    296     }
    297 
    298     private void updateF1Key() {
    299         if (mF1Key == null) return;
    300         if (m123Key != null && mIsAlphaKeyboard) {
    301             if (mVoiceEnabled && !mHasVoiceButton) {
    302                 m123Key.icon = m123MicIcon;
    303                 m123Key.iconPreview = m123MicPreviewIcon;
    304                 m123Key.label = null;
    305             } else {
    306                 m123Key.icon = null;
    307                 m123Key.iconPreview = null;
    308                 m123Key.label = m123Label;
    309             }
    310         }
    311 
    312         if (mHasVoiceButton && mVoiceEnabled) {
    313             mF1Key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE };
    314             mF1Key.label = null;
    315             mF1Key.icon = mMicIcon;
    316             mF1Key.iconPreview = mMicPreviewIcon;
    317         } else {
    318             mF1Key.label = ",";
    319             mF1Key.codes = new int[] { ',' };
    320             mF1Key.icon = null;
    321             mF1Key.iconPreview = null;
    322         }
    323     }
    324 
    325     private void updateSpaceBarForLocale() {
    326         if (mLocale != null) {
    327             // Create the graphic for spacebar
    328             Bitmap buffer = Bitmap.createBitmap(mSpaceKey.width, mSpaceIcon.getIntrinsicHeight(),
    329                     Bitmap.Config.ARGB_8888);
    330             Canvas canvas = new Canvas(buffer);
    331             drawSpaceBar(canvas, buffer.getWidth(), buffer.getHeight(), 255);
    332             mSpaceKey.icon = new BitmapDrawable(mRes, buffer);
    333             mSpaceKey.repeatable = mLanguageSwitcher.getLocaleCount() < 2;
    334         } else {
    335             mSpaceKey.icon = mRes.getDrawable(R.drawable.sym_keyboard_space);
    336             mSpaceKey.repeatable = true;
    337         }
    338     }
    339 
    340     private void drawSpaceBar(Canvas canvas, int width, int height, int opacity) {
    341         canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
    342         Paint paint = new Paint();
    343         paint.setAntiAlias(true);
    344         paint.setAlpha(opacity);
    345         // Get the text size from the theme
    346         paint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14));
    347         paint.setTextAlign(Align.CENTER);
    348         //// Draw a drop shadow for the text
    349         //paint.setShadowLayer(2f, 0, 0, 0xFF000000);
    350         final String language = getInputLanguage(mSpaceKey.width, paint);
    351         final int ascent = (int) -paint.ascent();
    352         paint.setColor(0x80000000);
    353         canvas.drawText(language,
    354                 width / 2, ascent - 1, paint);
    355         paint.setColor(0xFF808080);
    356         canvas.drawText(language,
    357                 width / 2, ascent, paint);
    358         // Put arrows on either side of the text
    359         if (mLanguageSwitcher.getLocaleCount() > 1) {
    360             Rect bounds = new Rect();
    361             paint.getTextBounds(language, 0, language.length(), bounds);
    362             drawButtonArrow(mButtonArrowLeftIcon, canvas,
    363                     (mSpaceKey.width - bounds.right) / 2
    364                     - mButtonArrowLeftIcon.getIntrinsicWidth(),
    365                     (int) paint.getTextSize());
    366             drawButtonArrow(mButtonArrowRightIcon, canvas,
    367                     (mSpaceKey.width + bounds.right) / 2, (int) paint.getTextSize());
    368         }
    369         // Draw the spacebar icon at the bottom
    370         int x = (width - mSpaceIcon.getIntrinsicWidth()) / 2;
    371         int y = height - mSpaceIcon.getIntrinsicHeight();
    372         mSpaceIcon.setBounds(x, y,
    373                 x + mSpaceIcon.getIntrinsicWidth(), y + mSpaceIcon.getIntrinsicHeight());
    374         mSpaceIcon.draw(canvas);
    375     }
    376 
    377     private void drawButtonArrow(Drawable arrow, Canvas canvas, int x, int bottomY) {
    378         arrow.setBounds(x, bottomY - arrow.getIntrinsicHeight(), x + arrow.getIntrinsicWidth(),
    379                 bottomY);
    380         arrow.draw(canvas);
    381     }
    382 
    383     private String getInputLanguage(int widthAvail, Paint paint) {
    384         return chooseDisplayName(mLanguageSwitcher.getInputLocale(), widthAvail, paint);
    385     }
    386 
    387     private String getNextInputLanguage(int widthAvail, Paint paint) {
    388         return chooseDisplayName(mLanguageSwitcher.getNextInputLocale(), widthAvail, paint);
    389     }
    390 
    391     private String getPrevInputLanguage(int widthAvail, Paint paint) {
    392         return chooseDisplayName(mLanguageSwitcher.getPrevInputLocale(), widthAvail, paint);
    393     }
    394 
    395     private String chooseDisplayName(Locale locale, int widthAvail, Paint paint) {
    396         if (widthAvail < (int) (.35 * getMinWidth())) {
    397             return LanguageSwitcher.toTitleCase(locale.getLanguage().substring(0, 2));
    398         } else {
    399             return LanguageSwitcher.toTitleCase(locale.getDisplayLanguage(locale));
    400         }
    401     }
    402 
    403     private void updateLocaleDrag(int diff) {
    404         if (mSlidingLocaleIcon == null) {
    405             mSlidingLocaleIcon = new SlidingLocaleDrawable(mSpacePreviewIcon, mSpaceKey.width,
    406                     mSpacePreviewIcon.getIntrinsicHeight());
    407             mSlidingLocaleIcon.setBounds(0, 0, mSpaceKey.width,
    408                     mSpacePreviewIcon.getIntrinsicHeight());
    409             mSpaceKey.iconPreview = mSlidingLocaleIcon;
    410         }
    411         mSlidingLocaleIcon.setDiff(diff);
    412         if (Math.abs(diff) == Integer.MAX_VALUE) {
    413             mSpaceKey.iconPreview = mSpacePreviewIcon;
    414         } else {
    415             mSpaceKey.iconPreview = mSlidingLocaleIcon;
    416         }
    417         mSpaceKey.iconPreview.invalidateSelf();
    418     }
    419 
    420     public int getLanguageChangeDirection() {
    421         if (mSpaceKey == null || mLanguageSwitcher.getLocaleCount() < 2
    422                 || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD ) {
    423             return 0; // No change
    424         }
    425         return mSpaceDragLastDiff > 0 ? 1 : -1;
    426     }
    427 
    428     public void setLanguageSwitcher(LanguageSwitcher switcher) {
    429         mLanguageSwitcher = switcher;
    430         Locale locale = mLanguageSwitcher.getLocaleCount() > 0
    431                 ? mLanguageSwitcher.getInputLocale()
    432                 : null;
    433         // If the language count is 1 and is the same as the system language, don't show it.
    434         if (locale != null
    435                 && mLanguageSwitcher.getLocaleCount() == 1
    436                 && mLanguageSwitcher.getSystemLocale().getLanguage()
    437                    .equalsIgnoreCase(locale.getLanguage())) {
    438             locale = null;
    439         }
    440         if (mLocale != null && mLocale.equals(locale)) return;
    441         mLocale = locale;
    442         updateSpaceBarForLocale();
    443     }
    444 
    445     boolean isCurrentlyInSpace() {
    446         return mCurrentlyInSpace;
    447     }
    448 
    449     void setPreferredLetters(int[] frequencies) {
    450         mPrefLetterFrequencies = frequencies;
    451         mPrefLetter = 0;
    452     }
    453 
    454     void keyReleased() {
    455         mCurrentlyInSpace = false;
    456         mSpaceDragLastDiff = 0;
    457         mPrefLetter = 0;
    458         mPrefLetterX = 0;
    459         mPrefLetterY = 0;
    460         mPrefDistance = Integer.MAX_VALUE;
    461         if (mSpaceKey != null) {
    462             updateLocaleDrag(Integer.MAX_VALUE);
    463         }
    464     }
    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 (code == LatinIME.KEYCODE_SPACE) {
    478             y += LatinKeyboard.sSpacebarVerticalCorrection;
    479             if (mLanguageSwitcher.getLocaleCount() > 1) {
    480                 if (mCurrentlyInSpace) {
    481                     int diff = x - mSpaceDragStartX;
    482                     if (Math.abs(diff - mSpaceDragLastDiff) > 0) {
    483                         updateLocaleDrag(diff);
    484                     }
    485                     mSpaceDragLastDiff = diff;
    486                     return true;
    487                 } else {
    488                     boolean insideSpace = key.isInsideSuper(x, y);
    489                     if (insideSpace) {
    490                         mCurrentlyInSpace = true;
    491                         mSpaceDragStartX = x;
    492                         updateLocaleDrag(0);
    493                     }
    494                     return insideSpace;
    495                 }
    496             }
    497         } else if (mPrefLetterFrequencies != null) {
    498             // New coordinate? Reset
    499             if (mPrefLetterX != x || mPrefLetterY != y) {
    500                 mPrefLetter = 0;
    501                 mPrefDistance = Integer.MAX_VALUE;
    502             }
    503             // Handle preferred next letter
    504             final int[] pref = mPrefLetterFrequencies;
    505             if (mPrefLetter > 0) {
    506                 if (DEBUG_PREFERRED_LETTER && mPrefLetter == code
    507                         && !key.isInsideSuper(x, y)) {
    508                     Log.d(TAG, "CORRECTED !!!!!!");
    509                 }
    510                 return mPrefLetter == code;
    511             } else {
    512                 final boolean inside = key.isInsideSuper(x, y);
    513                 int[] nearby = getNearestKeys(x, y);
    514                 List<Key> nearbyKeys = getKeys();
    515                 if (inside) {
    516                     // If it's a preferred letter
    517                     if (inPrefList(code, pref)) {
    518                         // Check if its frequency is much lower than a nearby key
    519                         mPrefLetter = code;
    520                         mPrefLetterX = x;
    521                         mPrefLetterY = y;
    522                         for (int i = 0; i < nearby.length; i++) {
    523                             Key k = nearbyKeys.get(nearby[i]);
    524                             if (k != key && inPrefList(k.codes[0], pref)) {
    525                                 final int dist = distanceFrom(k, x, y);
    526                                 if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) &&
    527                                         (pref[k.codes[0]] > pref[mPrefLetter] * 3))  {
    528                                     mPrefLetter = k.codes[0];
    529                                     mPrefDistance = dist;
    530                                     if (DEBUG_PREFERRED_LETTER) {
    531                                         Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!");
    532                                     }
    533                                     break;
    534                                 }
    535                             }
    536                         }
    537 
    538                         return mPrefLetter == code;
    539                     }
    540                 }
    541 
    542                 // Get the surrounding keys and intersect with the preferred list
    543                 // For all in the intersection
    544                 //   if distance from touch point is within a reasonable distance
    545                 //       make this the pref letter
    546                 // If no pref letter
    547                 //   return inside;
    548                 // else return thiskey == prefletter;
    549 
    550                 for (int i = 0; i < nearby.length; i++) {
    551                     Key k = nearbyKeys.get(nearby[i]);
    552                     if (inPrefList(k.codes[0], pref)) {
    553                         final int dist = distanceFrom(k, x, y);
    554                         if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB)
    555                                 && dist < mPrefDistance)  {
    556                             mPrefLetter = k.codes[0];
    557                             mPrefLetterX = x;
    558                             mPrefLetterY = y;
    559                             mPrefDistance = dist;
    560                         }
    561                     }
    562                 }
    563                 // Didn't find any
    564                 if (mPrefLetter == 0) {
    565                     return inside;
    566                 } else {
    567                     return mPrefLetter == code;
    568                 }
    569             }
    570         }
    571 
    572         // Lock into the spacebar
    573         if (mCurrentlyInSpace) return false;
    574 
    575         return key.isInsideSuper(x, y);
    576     }
    577 
    578     private boolean inPrefList(int code, int[] pref) {
    579         if (code < pref.length && code >= 0) return pref[code] > 0;
    580         return false;
    581     }
    582 
    583     private int distanceFrom(Key k, int x, int y) {
    584         if (y > k.y && y < k.y + k.height) {
    585             return Math.abs(k.x + k.width / 2 - x);
    586         } else {
    587             return Integer.MAX_VALUE;
    588         }
    589     }
    590 
    591     @Override
    592     public int[] getNearestKeys(int x, int y) {
    593         if (mCurrentlyInSpace) {
    594             return new int[] { mSpaceKeyIndex };
    595         } else {
    596             return super.getNearestKeys(x, y);
    597         }
    598     }
    599 
    600     private int indexOf(int code) {
    601         List<Key> keys = getKeys();
    602         int count = keys.size();
    603         for (int i = 0; i < count; i++) {
    604             if (keys.get(i).codes[0] == code) return i;
    605         }
    606         return -1;
    607     }
    608 
    609     private int getTextSizeFromTheme(int style, int defValue) {
    610         TypedArray array = mContext.getTheme().obtainStyledAttributes(
    611                 style, new int[] { android.R.attr.textSize });
    612         int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
    613         return textSize;
    614     }
    615 
    616     class LatinKey extends Keyboard.Key {
    617 
    618         private boolean mShiftLockEnabled;
    619 
    620         public LatinKey(Resources res, Keyboard.Row parent, int x, int y,
    621                 XmlResourceParser parser) {
    622             super(res, parent, x, y, parser);
    623             if (popupCharacters != null && popupCharacters.length() == 0) {
    624                 // If there is a keyboard with no keys specified in popupCharacters
    625                 popupResId = 0;
    626             }
    627         }
    628 
    629         void enableShiftLock() {
    630             mShiftLockEnabled = true;
    631         }
    632 
    633         @Override
    634         public void onReleased(boolean inside) {
    635             if (!mShiftLockEnabled) {
    636                 super.onReleased(inside);
    637             } else {
    638                 pressed = !pressed;
    639             }
    640         }
    641 
    642         /**
    643          * Overriding this method so that we can reduce the target area for certain keys.
    644          */
    645         @Override
    646         public boolean isInside(int x, int y) {
    647             boolean result = LatinKeyboard.this.isInside(this, x, y);
    648             return result;
    649         }
    650 
    651         boolean isInsideSuper(int x, int y) {
    652             return super.isInside(x, y);
    653         }
    654     }
    655 
    656     /**
    657      * Animation to be displayed on the spacebar preview popup when switching
    658      * languages by swiping the spacebar. It draws the current, previous and
    659      * next languages and moves them by the delta of touch movement on the spacebar.
    660      */
    661     class SlidingLocaleDrawable extends Drawable {
    662 
    663         private int mWidth;
    664         private int mHeight;
    665         private Drawable mBackground;
    666         private int mDiff;
    667         private TextPaint mTextPaint;
    668         private int mMiddleX;
    669         private int mAscent;
    670         private Drawable mLeftDrawable;
    671         private Drawable mRightDrawable;
    672         private boolean mHitThreshold;
    673         private int     mThreshold;
    674         private String mCurrentLanguage;
    675         private String mNextLanguage;
    676         private String mPrevLanguage;
    677 
    678         public SlidingLocaleDrawable(Drawable background, int width, int height) {
    679             mBackground = background;
    680             mBackground.setBounds(0, 0,
    681                     mBackground.getIntrinsicWidth(), mBackground.getIntrinsicHeight());
    682             mWidth = width;
    683             mHeight = height;
    684             mTextPaint = new TextPaint();
    685             int textSize = getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18);
    686             mTextPaint.setTextSize(textSize);
    687             mTextPaint.setColor(0);
    688             mTextPaint.setTextAlign(Align.CENTER);
    689             mTextPaint.setAlpha(255);
    690             mTextPaint.setAntiAlias(true);
    691             mAscent = (int) mTextPaint.ascent();
    692             mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2;
    693             mLeftDrawable =
    694                     mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_left);
    695             mRightDrawable =
    696                     mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_right);
    697             mLeftDrawable.setBounds(0, 0,
    698                     mLeftDrawable.getIntrinsicWidth(), mLeftDrawable.getIntrinsicHeight());
    699             mRightDrawable.setBounds(mWidth - mRightDrawable.getIntrinsicWidth(), 0,
    700                     mWidth, mRightDrawable.getIntrinsicHeight());
    701             mThreshold = ViewConfiguration.get(mContext).getScaledTouchSlop();
    702         }
    703 
    704         void setDiff(int diff) {
    705             if (diff == Integer.MAX_VALUE) {
    706                 mHitThreshold = false;
    707                 mCurrentLanguage = null;
    708                 return;
    709             }
    710             mDiff = diff;
    711             if (mDiff > mWidth) mDiff = mWidth;
    712             if (mDiff < -mWidth) mDiff = -mWidth;
    713             if (Math.abs(mDiff) > mThreshold) mHitThreshold = true;
    714             invalidateSelf();
    715         }
    716 
    717         @Override
    718         public void draw(Canvas canvas) {
    719             canvas.save();
    720             if (mHitThreshold) {
    721                 mTextPaint.setColor(0xFF000000);
    722                 canvas.clipRect(0, 0, mWidth, mHeight);
    723                 if (mCurrentLanguage == null) {
    724                     mCurrentLanguage = getInputLanguage(mWidth, mTextPaint);
    725                     mNextLanguage = getNextInputLanguage(mWidth, mTextPaint);
    726                     mPrevLanguage = getPrevInputLanguage(mWidth, mTextPaint);
    727                 }
    728                 canvas.drawText(mCurrentLanguage,
    729                         mWidth / 2 + mDiff, -mAscent + 4, mTextPaint);
    730                 canvas.drawText(mNextLanguage,
    731                         mDiff - mWidth / 2, -mAscent + 4, mTextPaint);
    732                 canvas.drawText(mPrevLanguage,
    733                         mDiff + mWidth + mWidth / 2, -mAscent + 4, mTextPaint);
    734                 mLeftDrawable.draw(canvas);
    735                 mRightDrawable.draw(canvas);
    736             }
    737             if (mBackground != null) {
    738                 canvas.translate(mMiddleX, 0);
    739                 mBackground.draw(canvas);
    740             }
    741             canvas.restore();
    742         }
    743 
    744         @Override
    745         public int getOpacity() {
    746             return PixelFormat.TRANSLUCENT;
    747         }
    748 
    749         @Override
    750         public void setAlpha(int alpha) {
    751             // Ignore
    752         }
    753 
    754         @Override
    755         public void setColorFilter(ColorFilter cf) {
    756             // Ignore
    757         }
    758 
    759         @Override
    760         public int getIntrinsicWidth() {
    761             return mWidth;
    762         }
    763 
    764         @Override
    765         public int getIntrinsicHeight() {
    766             return mHeight;
    767         }
    768     }
    769 }
    770