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