Home | History | Annotate | Download | only in keyboard
      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 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.content.res.Resources.Theme;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Bitmap;
     24 import android.graphics.Canvas;
     25 import android.graphics.Color;
     26 import android.graphics.Paint;
     27 import android.graphics.Paint.Align;
     28 import android.graphics.PorterDuff;
     29 import android.graphics.Rect;
     30 import android.graphics.drawable.BitmapDrawable;
     31 import android.graphics.drawable.Drawable;
     32 import android.text.TextUtils;
     33 
     34 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
     35 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
     36 import com.android.inputmethod.keyboard.internal.KeyboardParams;
     37 import com.android.inputmethod.latin.R;
     38 import com.android.inputmethod.latin.SubtypeSwitcher;
     39 import com.android.inputmethod.latin.Utils;
     40 
     41 import java.lang.ref.SoftReference;
     42 import java.util.Arrays;
     43 import java.util.HashMap;
     44 import java.util.Locale;
     45 
     46 // TODO: We should remove this class
     47 public class LatinKeyboard extends Keyboard {
     48     private static final int SPACE_LED_LENGTH_PERCENT = 80;
     49 
     50     private final Resources mRes;
     51     private final Theme mTheme;
     52     private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
     53 
     54     /* Space key and its icons, drawables and colors. */
     55     private final Key mSpaceKey;
     56     private final Drawable mSpaceIcon;
     57     private final boolean mAutoCorrectionSpacebarLedEnabled;
     58     private final Drawable mAutoCorrectionSpacebarLedIcon;
     59     private final int mSpacebarTextColor;
     60     private final int mSpacebarTextShadowColor;
     61     private float mSpacebarTextFadeFactor = 0.0f;
     62     private final HashMap<Integer, BitmapDrawable> mSpaceDrawableCache =
     63             new HashMap<Integer, BitmapDrawable>();
     64     private final boolean mIsSpacebarTriggeringPopupByLongPress;
     65 
     66     /* Shortcut key and its icons if available */
     67     private final Key mShortcutKey;
     68     private final Drawable mEnabledShortcutIcon;
     69     private final Drawable mDisabledShortcutIcon;
     70 
     71     // Height in space key the language name will be drawn. (proportional to space key height)
     72     public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
     73     // If the full language name needs to be smaller than this value to be drawn on space key,
     74     // its short language name will be used instead.
     75     private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
     76 
     77     private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small";
     78     private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
     79 
     80     private LatinKeyboard(Context context, LatinKeyboardParams params) {
     81         super(params);
     82         mRes = context.getResources();
     83         mTheme = context.getTheme();
     84 
     85         // The index of space key is available only after Keyboard constructor has finished.
     86         mSpaceKey = params.mSpaceKey;
     87         mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
     88 
     89         mShortcutKey = params.mShortcutKey;
     90         mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
     91         final int longPressSpaceKeyTimeout =
     92                 mRes.getInteger(R.integer.config_long_press_space_key_timeout);
     93         mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0);
     94 
     95         final TypedArray a = context.obtainStyledAttributes(
     96                 null, R.styleable.LatinKeyboard, R.attr.latinKeyboardStyle, R.style.LatinKeyboard);
     97         mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
     98                 R.styleable.LatinKeyboard_autoCorrectionSpacebarLedEnabled, false);
     99         mAutoCorrectionSpacebarLedIcon = a.getDrawable(
    100                 R.styleable.LatinKeyboard_autoCorrectionSpacebarLedIcon);
    101         mDisabledShortcutIcon = a.getDrawable(R.styleable.LatinKeyboard_disabledShortcutIcon);
    102         mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboard_spacebarTextColor, 0);
    103         mSpacebarTextShadowColor = a.getColor(
    104                 R.styleable.LatinKeyboard_spacebarTextShadowColor, 0);
    105         a.recycle();
    106     }
    107 
    108     private static class LatinKeyboardParams extends KeyboardParams {
    109         public Key mSpaceKey = null;
    110         public Key mShortcutKey = null;
    111 
    112         @Override
    113         public void onAddKey(Key key) {
    114             super.onAddKey(key);
    115 
    116             switch (key.mCode) {
    117             case Keyboard.CODE_SPACE:
    118                 mSpaceKey = key;
    119                 break;
    120             case Keyboard.CODE_SHORTCUT:
    121                 mShortcutKey = key;
    122                 break;
    123             }
    124         }
    125     }
    126 
    127     public static class Builder extends KeyboardBuilder<LatinKeyboardParams> {
    128         public Builder(Context context) {
    129             super(context, new LatinKeyboardParams());
    130         }
    131 
    132         @Override
    133         public Builder load(KeyboardId id) {
    134             super.load(id);
    135             return this;
    136         }
    137 
    138         @Override
    139         public LatinKeyboard build() {
    140             return new LatinKeyboard(mContext, mParams);
    141         }
    142     }
    143 
    144     public void setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view) {
    145         mSpacebarTextFadeFactor = fadeFactor;
    146         updateSpacebarForLocale(false);
    147         if (view != null)
    148             view.invalidateKey(mSpaceKey);
    149     }
    150 
    151     private static int getSpacebarTextColor(int color, float fadeFactor) {
    152         final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
    153                 Color.red(color), Color.green(color), Color.blue(color));
    154         return newColor;
    155     }
    156 
    157     public void updateShortcutKey(boolean available, KeyboardView view) {
    158         if (mShortcutKey == null)
    159             return;
    160         mShortcutKey.setEnabled(available);
    161         mShortcutKey.setIcon(available ? mEnabledShortcutIcon : mDisabledShortcutIcon);
    162         if (view != null)
    163             view.invalidateKey(mShortcutKey);
    164     }
    165 
    166     public boolean needsAutoCorrectionSpacebarLed() {
    167         return mAutoCorrectionSpacebarLedEnabled;
    168     }
    169 
    170     /**
    171      * @return a key which should be invalidated.
    172      */
    173     public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) {
    174         updateSpacebarForLocale(isAutoCorrection);
    175         return mSpaceKey;
    176     }
    177 
    178     @Override
    179     public CharSequence adjustLabelCase(CharSequence label) {
    180         if (isAlphaKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label)
    181                 && label.length() < 3 && Character.isLowerCase(label.charAt(0))) {
    182             return label.toString().toUpperCase(mId.mLocale);
    183         }
    184         return label;
    185     }
    186 
    187     private void updateSpacebarForLocale(boolean isAutoCorrection) {
    188         if (mSpaceKey == null) return;
    189         final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
    190         if (imm == null) return;
    191         // The "..." popup hint for triggering something by a long-pressing the spacebar
    192         final boolean shouldShowInputMethodPicker = mIsSpacebarTriggeringPopupByLongPress
    193                 && Utils.hasMultipleEnabledIMEsOrSubtypes(imm, true /* include aux subtypes */);
    194         mSpaceKey.setNeedsSpecialPopupHint(shouldShowInputMethodPicker);
    195         // If application locales are explicitly selected.
    196         if (mSubtypeSwitcher.needsToDisplayLanguage(mId.mLocale)) {
    197             mSpaceKey.setIcon(getSpaceDrawable(mId.mLocale, isAutoCorrection));
    198         } else if (isAutoCorrection) {
    199             mSpaceKey.setIcon(getSpaceDrawable(null, true));
    200         } else {
    201             mSpaceKey.setIcon(mSpaceIcon);
    202         }
    203     }
    204 
    205     // Compute width of text with specified text size using paint.
    206     private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) {
    207         paint.setTextSize(textSize);
    208         paint.getTextBounds(text, 0, text.length(), bounds);
    209         return bounds.width();
    210     }
    211 
    212     // Layout local language name and left and right arrow on spacebar.
    213     private static String layoutSpacebar(Paint paint, Locale locale, int width,
    214             float origTextSize) {
    215         final Rect bounds = new Rect();
    216 
    217         // Estimate appropriate language name text size to fit in maxTextWidth.
    218         String language = Utils.getFullDisplayName(locale, true);
    219         int textWidth = getTextWidth(paint, language, origTextSize, bounds);
    220         // Assuming text width and text size are proportional to each other.
    221         float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
    222         // allow variable text size
    223         textWidth = getTextWidth(paint, language, textSize, bounds);
    224         // If text size goes too small or text does not fit, use middle or short name
    225         final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
    226                 || (textWidth > width);
    227 
    228         final boolean useShortName;
    229         if (useMiddleName) {
    230             language = Utils.getMiddleDisplayLanguage(locale);
    231             textWidth = getTextWidth(paint, language, origTextSize, bounds);
    232             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
    233             useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
    234                     || (textWidth > width);
    235         } else {
    236             useShortName = false;
    237         }
    238 
    239         if (useShortName) {
    240             language = Utils.getShortDisplayLanguage(locale);
    241             textWidth = getTextWidth(paint, language, origTextSize, bounds);
    242             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
    243         }
    244         paint.setTextSize(textSize);
    245 
    246         return language;
    247     }
    248 
    249     private BitmapDrawable getSpaceDrawable(Locale locale, boolean isAutoCorrection) {
    250         final Integer hashCode = Arrays.hashCode(
    251                 new Object[] { locale, isAutoCorrection, mSpacebarTextFadeFactor });
    252         final BitmapDrawable cached = mSpaceDrawableCache.get(hashCode);
    253         if (cached != null) {
    254             return cached;
    255         }
    256         final BitmapDrawable drawable = new BitmapDrawable(mRes, drawSpacebar(
    257                 locale, isAutoCorrection, mSpacebarTextFadeFactor));
    258         mSpaceDrawableCache.put(hashCode, drawable);
    259         return drawable;
    260     }
    261 
    262     private Bitmap drawSpacebar(Locale inputLocale, boolean isAutoCorrection,
    263             float textFadeFactor) {
    264         final int width = mSpaceKey.mWidth;
    265         final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight;
    266         final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    267         final Canvas canvas = new Canvas(buffer);
    268         final Resources res = mRes;
    269         canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    270 
    271         // If application locales are explicitly selected.
    272         if (inputLocale != null) {
    273             final Paint paint = new Paint();
    274             paint.setAntiAlias(true);
    275             paint.setTextAlign(Align.CENTER);
    276 
    277             final String textSizeOfLanguageOnSpacebar = res.getString(
    278                     R.string.config_text_size_of_language_on_spacebar,
    279                     SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR);
    280             final int textStyle;
    281             final int defaultTextSize;
    282             if (MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR.equals(textSizeOfLanguageOnSpacebar)) {
    283                 textStyle = android.R.style.TextAppearance_Medium;
    284                 defaultTextSize = 18;
    285             } else {
    286                 textStyle = android.R.style.TextAppearance_Small;
    287                 defaultTextSize = 14;
    288             }
    289 
    290             final String language = layoutSpacebar(paint, inputLocale, width, getTextSizeFromTheme(
    291                     mTheme, textStyle, defaultTextSize));
    292 
    293             // Draw language text with shadow
    294             // In case there is no space icon, we will place the language text at the center of
    295             // spacebar.
    296             final float descent = paint.descent();
    297             final float textHeight = -paint.ascent() + descent;
    298             final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE
    299                     : height / 2 + textHeight / 2;
    300             paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, textFadeFactor));
    301             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
    302             paint.setColor(getSpacebarTextColor(mSpacebarTextColor, textFadeFactor));
    303             canvas.drawText(language, width / 2, baseline - descent, paint);
    304         }
    305 
    306         // Draw the spacebar icon at the bottom
    307         if (isAutoCorrection) {
    308             final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
    309             final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
    310             int x = (width - iconWidth) / 2;
    311             int y = height - iconHeight;
    312             mAutoCorrectionSpacebarLedIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
    313             mAutoCorrectionSpacebarLedIcon.draw(canvas);
    314         } else if (mSpaceIcon != null) {
    315             final int iconWidth = mSpaceIcon.getIntrinsicWidth();
    316             final int iconHeight = mSpaceIcon.getIntrinsicHeight();
    317             int x = (width - iconWidth) / 2;
    318             int y = height - iconHeight;
    319             mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
    320             mSpaceIcon.draw(canvas);
    321         }
    322         return buffer;
    323     }
    324 
    325     @Override
    326     public int[] getNearestKeys(int x, int y) {
    327         // Avoid dead pixels at edges of the keyboard
    328         return super.getNearestKeys(Math.max(0, Math.min(x, mOccupiedWidth - 1)),
    329                 Math.max(0, Math.min(y, mOccupiedHeight - 1)));
    330     }
    331 
    332     public static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
    333         TypedArray array = theme.obtainStyledAttributes(
    334                 style, new int[] { android.R.attr.textSize });
    335         int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
    336         return textSize;
    337     }
    338 }
    339