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