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