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 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 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> { 128 public Builder(Context context) { 129 super(context, new LatinKeyboardParams()); 130 } 131 132 @Override 133 public Builder load(KeyboardId id) { 134 super.load(id); 135 return this; 136 } 137 138 @Override 139 public LatinKeyboard build() { 140 return new LatinKeyboard(mContext, mParams); 141 } 142 } 143 144 public void setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view) { 145 mSpacebarTextFadeFactor = fadeFactor; 146 updateSpacebarForLocale(false); 147 if (view != null) 148 view.invalidateKey(mSpaceKey); 149 } 150 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 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 166 public boolean needsAutoCorrectionSpacebarLed() { 167 return mAutoCorrectionSpacebarLedEnabled; 168 } 169 170 /** 171 * @return a key which should be invalidated. 172 */ 173 public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) { 174 updateSpacebarForLocale(isAutoCorrection); 175 return mSpaceKey; 176 } 177 178 @Override 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 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. 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. 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 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 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 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 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