• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LatinKeyboard(Context context, LatinKeyboardParams params)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
onAddKey(Key key)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> {
Builder(Context context)128         public Builder(Context context) {
129             super(context, new LatinKeyboardParams());
130         }
131 
132         @Override
load(KeyboardId id)133         public Builder load(KeyboardId id) {
134             super.load(id);
135             return this;
136         }
137 
138         @Override
build()139         public LatinKeyboard build() {
140             return new LatinKeyboard(mContext, mParams);
141         }
142     }
143 
setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view)144     public void setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view) {
145         mSpacebarTextFadeFactor = fadeFactor;
146         updateSpacebarForLocale(false);
147         if (view != null)
148             view.invalidateKey(mSpaceKey);
149     }
150 
getSpacebarTextColor(int color, float fadeFactor)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 
updateShortcutKey(boolean available, KeyboardView view)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 
needsAutoCorrectionSpacebarLed()166     public boolean needsAutoCorrectionSpacebarLed() {
167         return mAutoCorrectionSpacebarLedEnabled;
168     }
169 
170     /**
171      * @return a key which should be invalidated.
172      */
onAutoCorrectionStateChanged(boolean isAutoCorrection)173     public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) {
174         updateSpacebarForLocale(isAutoCorrection);
175         return mSpaceKey;
176     }
177 
178     @Override
adjustLabelCase(CharSequence label)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 
updateSpacebarForLocale(boolean isAutoCorrection)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.
getTextWidth(Paint paint, String text, float textSize, Rect bounds)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.
layoutSpacebar(Paint paint, Locale locale, int width, float origTextSize)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 
getSpaceDrawable(Locale locale, boolean isAutoCorrection)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 
drawSpacebar(Locale inputLocale, boolean isAutoCorrection, float textFadeFactor)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
getNearestKeys(int x, int y)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 
getTextSizeFromTheme(Theme theme, int style, int defValue)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