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