LatinKeyboard.java revision 715189fe6eaa1795e38b461ed4b5860097598275
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 com.android.inputmethod.latin.R; 20import com.android.inputmethod.latin.SubtypeSwitcher; 21 22import android.content.Context; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.graphics.Paint; 28import android.graphics.Paint.Align; 29import android.graphics.PorterDuff; 30import android.graphics.Rect; 31import android.graphics.drawable.BitmapDrawable; 32import android.graphics.drawable.Drawable; 33import android.util.Log; 34 35import java.util.List; 36import java.util.Locale; 37 38// TODO: We should remove this class 39public class LatinKeyboard extends Keyboard { 40 41 private static final boolean DEBUG_PREFERRED_LETTER = false; 42 private static final String TAG = "LatinKeyboard"; 43 44 public static final int OPACITY_FULLY_OPAQUE = 255; 45 private static final int SPACE_LED_LENGTH_PERCENT = 80; 46 47 private final Drawable mSpaceAutoCorrectionIndicator; 48 private final Drawable mButtonArrowLeftIcon; 49 private final Drawable mButtonArrowRightIcon; 50 private final int mSpaceBarTextShadowColor; 51 private int mSpaceKeyIndex = -1; 52 private int mSpaceDragStartX; 53 private int mSpaceDragLastDiff; 54 private final Context mContext; 55 private boolean mCurrentlyInSpace; 56 private SlidingLocaleDrawable mSlidingLocaleIcon; 57 private int[] mPrefLetterFrequencies; 58 private int mPrefLetter; 59 private int mPrefLetterX; 60 private int mPrefLetterY; 61 private int mPrefDistance; 62 63 private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; 64 private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; 65 private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; 66 // Minimum width of space key preview (proportional to keyboard width) 67 private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f; 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 int sSpacebarVerticalCorrection; 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 public LatinKeyboard(Context context, KeyboardId id) { 80 super(context, id.getXmlId(), id); 81 final Resources res = context.getResources(); 82 mContext = context; 83 if (id.mColorScheme == KeyboardView.COLOR_SCHEME_BLACK) { 84 mSpaceBarTextShadowColor = res.getColor( 85 R.color.latinkeyboard_bar_language_shadow_black); 86 } else { // default color scheme is KeyboardView.COLOR_SCHEME_WHITE 87 mSpaceBarTextShadowColor = res.getColor( 88 R.color.latinkeyboard_bar_language_shadow_white); 89 } 90 mSpaceAutoCorrectionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led); 91 mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left); 92 mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right); 93 sSpacebarVerticalCorrection = res.getDimensionPixelOffset( 94 R.dimen.spacebar_vertical_correction); 95 mSpaceKeyIndex = indexOf(CODE_SPACE); 96 } 97 98 /** 99 * @return a key which should be invalidated. 100 */ 101 public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) { 102 updateSpaceBarForLocale(isAutoCorrection); 103 return mSpaceKey; 104 } 105 106 private void updateSpaceBarForLocale(boolean isAutoCorrection) { 107 final Resources res = mContext.getResources(); 108 // If application locales are explicitly selected. 109 if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) { 110 mSpaceKey.setIcon(new BitmapDrawable(res, 111 drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCorrection))); 112 } else { 113 // sym_keyboard_space_led can be shared with Black and White symbol themes. 114 if (isAutoCorrection) { 115 mSpaceKey.setIcon(new BitmapDrawable(res, 116 drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCorrection))); 117 } else { 118 mSpaceKey.setIcon(mSpaceIcon); 119 } 120 } 121 } 122 123 // Compute width of text with specified text size using paint. 124 private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) { 125 paint.setTextSize(textSize); 126 paint.getTextBounds(text, 0, text.length(), bounds); 127 return bounds.width(); 128 } 129 130 // Layout local language name and left and right arrow on space bar. 131 private static String layoutSpaceBar(Paint paint, Locale locale, Drawable lArrow, 132 Drawable rArrow, int width, int height, float origTextSize, 133 boolean allowVariableTextSize) { 134 final float arrowWidth = lArrow.getIntrinsicWidth(); 135 final float arrowHeight = lArrow.getIntrinsicHeight(); 136 final float maxTextWidth = width - (arrowWidth + arrowWidth); 137 final Rect bounds = new Rect(); 138 139 // Estimate appropriate language name text size to fit in maxTextWidth. 140 String language = SubtypeSwitcher.getDisplayLanguage(locale); 141 int textWidth = getTextWidth(paint, language, origTextSize, bounds); 142 // Assuming text width and text size are proportional to each other. 143 float textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); 144 145 final boolean useShortName; 146 if (allowVariableTextSize) { 147 textWidth = getTextWidth(paint, language, textSize, bounds); 148 // If text size goes too small or text does not fit, use short name 149 useShortName = textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME 150 || textWidth > maxTextWidth; 151 } else { 152 useShortName = textWidth > maxTextWidth; 153 textSize = origTextSize; 154 } 155 if (useShortName) { 156 language = SubtypeSwitcher.getShortDisplayLanguage(locale); 157 textWidth = getTextWidth(paint, language, origTextSize, bounds); 158 textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); 159 } 160 paint.setTextSize(textSize); 161 162 // Place left and right arrow just before and after language text. 163 final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; 164 final int top = (int)(baseline - arrowHeight); 165 final float remains = (width - textWidth) / 2; 166 lArrow.setBounds((int)(remains - arrowWidth), top, (int)remains, (int)baseline); 167 rArrow.setBounds((int)(remains + textWidth), top, (int)(remains + textWidth + arrowWidth), 168 (int)baseline); 169 170 return language; 171 } 172 173 @SuppressWarnings("unused") 174 private Bitmap drawSpaceBar(int opacity, boolean isAutoCorrection) { 175 final int width = mSpaceKey.mWidth; 176 final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight; 177 final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 178 final Canvas canvas = new Canvas(buffer); 179 final Resources res = mContext.getResources(); 180 canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR); 181 182 SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance(); 183 // If application locales are explicitly selected. 184 if (subtypeSwitcher.needsToDisplayLanguage()) { 185 final Paint paint = new Paint(); 186 paint.setAlpha(opacity); 187 paint.setAntiAlias(true); 188 paint.setTextAlign(Align.CENTER); 189 190 final String textSizeOfLanguageOnSpacebar = res.getString( 191 R.string.config_text_size_of_language_on_spacebar, 192 SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR); 193 final int textStyle; 194 final int defaultTextSize; 195 if (MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR.equals(textSizeOfLanguageOnSpacebar)) { 196 textStyle = android.R.style.TextAppearance_Medium; 197 defaultTextSize = 18; 198 } else { 199 textStyle = android.R.style.TextAppearance_Small; 200 defaultTextSize = 14; 201 } 202 203 final boolean allowVariableTextSize = true; 204 final String language = layoutSpaceBar(paint, subtypeSwitcher.getInputLocale(), 205 mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height, 206 getTextSizeFromTheme(textStyle, defaultTextSize), 207 allowVariableTextSize); 208 209 // Draw language text with shadow 210 final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; 211 final float descent = paint.descent(); 212 paint.setColor(mSpaceBarTextShadowColor); 213 canvas.drawText(language, width / 2, baseline - descent - 1, paint); 214 paint.setColor(res.getColor(R.color.latinkeyboard_bar_language_text)); 215 canvas.drawText(language, width / 2, baseline - descent, paint); 216 217 // Put arrows that are already layed out on either side of the text 218 if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER 219 && subtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { 220 mButtonArrowLeftIcon.draw(canvas); 221 mButtonArrowRightIcon.draw(canvas); 222 } 223 } 224 225 // Draw the spacebar icon at the bottom 226 if (isAutoCorrection) { 227 final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; 228 final int iconHeight = mSpaceAutoCorrectionIndicator.getIntrinsicHeight(); 229 int x = (width - iconWidth) / 2; 230 int y = height - iconHeight; 231 mSpaceAutoCorrectionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight); 232 mSpaceAutoCorrectionIndicator.draw(canvas); 233 } else if (mSpaceIcon != null) { 234 final int iconWidth = mSpaceIcon.getIntrinsicWidth(); 235 final int iconHeight = mSpaceIcon.getIntrinsicHeight(); 236 int x = (width - iconWidth) / 2; 237 int y = height - iconHeight; 238 mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight); 239 mSpaceIcon.draw(canvas); 240 } 241 return buffer; 242 } 243 244 private void updateLocaleDrag(int diff) { 245 if (mSlidingLocaleIcon == null) { 246 final int width = Math.max(mSpaceKey.mWidth, 247 (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO)); 248 final int height = mSpacePreviewIcon.getIntrinsicHeight(); 249 mSlidingLocaleIcon = 250 new SlidingLocaleDrawable(mContext, mSpacePreviewIcon, width, height); 251 mSlidingLocaleIcon.setBounds(0, 0, width, height); 252 mSpaceKey.setPreviewIcon(mSlidingLocaleIcon); 253 } 254 mSlidingLocaleIcon.setDiff(diff); 255 if (Math.abs(diff) == Integer.MAX_VALUE) { 256 mSpaceKey.setPreviewIcon(mSpacePreviewIcon); 257 } else { 258 mSpaceKey.setPreviewIcon(mSlidingLocaleIcon); 259 } 260 mSpaceKey.getPreviewIcon().invalidateSelf(); 261 } 262 263 public int getLanguageChangeDirection() { 264 if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1 265 || Math.abs(mSpaceDragLastDiff) < mSpaceKey.mWidth * SPACEBAR_DRAG_THRESHOLD) { 266 return 0; // No change 267 } 268 return mSpaceDragLastDiff > 0 ? 1 : -1; 269 } 270 271 public void setPreferredLetters(int[] frequencies) { 272 mPrefLetterFrequencies = frequencies; 273 mPrefLetter = 0; 274 } 275 276 public void keyReleased() { 277 mCurrentlyInSpace = false; 278 mSpaceDragLastDiff = 0; 279 mPrefLetter = 0; 280 mPrefLetterX = 0; 281 mPrefLetterY = 0; 282 mPrefDistance = Integer.MAX_VALUE; 283 if (mSpaceKey != null) { 284 updateLocaleDrag(Integer.MAX_VALUE); 285 } 286 } 287 288 /** 289 * Does the magic of locking the touch gesture into the spacebar when 290 * switching input languages. 291 */ 292 @Override 293 @SuppressWarnings("unused") // SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER is constant 294 public boolean isInside(Key key, int pointX, int pointY) { 295 int x = pointX; 296 int y = pointY; 297 final int code = key.mCode; 298 if (code == CODE_SHIFT || code == CODE_DELETE) { 299 y -= key.mHeight / 10; 300 if (code == CODE_SHIFT) x += key.mWidth / 6; 301 if (code == CODE_DELETE) x -= key.mWidth / 6; 302 } else if (code == CODE_SPACE) { 303 y += LatinKeyboard.sSpacebarVerticalCorrection; 304 if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER 305 && SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) { 306 if (mCurrentlyInSpace) { 307 int diff = x - mSpaceDragStartX; 308 if (Math.abs(diff - mSpaceDragLastDiff) > 0) { 309 updateLocaleDrag(diff); 310 } 311 mSpaceDragLastDiff = diff; 312 return true; 313 } else { 314 boolean isOnSpace = key.isOnKey(x, y); 315 if (isOnSpace) { 316 mCurrentlyInSpace = true; 317 mSpaceDragStartX = x; 318 updateLocaleDrag(0); 319 } 320 return isOnSpace; 321 } 322 } 323 } else if (mPrefLetterFrequencies != null) { 324 // New coordinate? Reset 325 if (mPrefLetterX != x || mPrefLetterY != y) { 326 mPrefLetter = 0; 327 mPrefDistance = Integer.MAX_VALUE; 328 } 329 // Handle preferred next letter 330 final int[] pref = mPrefLetterFrequencies; 331 if (mPrefLetter > 0) { 332 if (DEBUG_PREFERRED_LETTER) { 333 if (mPrefLetter == code && !key.isOnKey(x, y)) { 334 Log.d(TAG, "CORRECTED !!!!!!"); 335 } 336 } 337 return mPrefLetter == code; 338 } else { 339 final boolean isOnKey = key.isOnKey(x, y); 340 int[] nearby = getNearestKeys(x, y); 341 List<Key> nearbyKeys = getKeys(); 342 if (isOnKey) { 343 // If it's a preferred letter 344 if (inPrefList(code, pref)) { 345 // Check if its frequency is much lower than a nearby key 346 mPrefLetter = code; 347 mPrefLetterX = x; 348 mPrefLetterY = y; 349 for (int i = 0; i < nearby.length; i++) { 350 Key k = nearbyKeys.get(nearby[i]); 351 if (k != key && inPrefList(k.mCode, pref)) { 352 final int dist = distanceFrom(k, x, y); 353 if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_LOW_PROB) && 354 (pref[k.mCode] > pref[mPrefLetter] * 3)) { 355 mPrefLetter = k.mCode; 356 mPrefDistance = dist; 357 if (DEBUG_PREFERRED_LETTER) { 358 Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); 359 } 360 break; 361 } 362 } 363 } 364 365 return mPrefLetter == code; 366 } 367 } 368 369 // Get the surrounding keys and intersect with the preferred list 370 // For all in the intersection 371 // if distance from touch point is within a reasonable distance 372 // make this the pref letter 373 // If no pref letter 374 // return inside; 375 // else return thiskey == prefletter; 376 377 for (int i = 0; i < nearby.length; i++) { 378 Key k = nearbyKeys.get(nearby[i]); 379 if (inPrefList(k.mCode, pref)) { 380 final int dist = distanceFrom(k, x, y); 381 if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_HIGH_PROB) 382 && dist < mPrefDistance) { 383 mPrefLetter = k.mCode; 384 mPrefLetterX = x; 385 mPrefLetterY = y; 386 mPrefDistance = dist; 387 } 388 } 389 } 390 // Didn't find any 391 if (mPrefLetter == 0) { 392 return isOnKey; 393 } else { 394 return mPrefLetter == code; 395 } 396 } 397 } 398 399 // Lock into the spacebar 400 if (mCurrentlyInSpace) return false; 401 402 return key.isOnKey(x, y); 403 } 404 405 private boolean inPrefList(int code, int[] pref) { 406 if (code < pref.length && code >= 0) return pref[code] > 0; 407 return false; 408 } 409 410 private int distanceFrom(Key k, int x, int y) { 411 if (y > k.mY && y < k.mY + k.mHeight) { 412 return Math.abs(k.mX + k.mWidth / 2 - x); 413 } else { 414 return Integer.MAX_VALUE; 415 } 416 } 417 418 @Override 419 public int[] getNearestKeys(int x, int y) { 420 if (mCurrentlyInSpace) { 421 return new int[] { mSpaceKeyIndex }; 422 } else { 423 // Avoid dead pixels at edges of the keyboard 424 return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)), 425 Math.max(0, Math.min(y, getHeight() - 1))); 426 } 427 } 428 429 private int indexOf(int code) { 430 List<Key> keys = getKeys(); 431 int count = keys.size(); 432 for (int i = 0; i < count; i++) { 433 if (keys.get(i).mCode == code) return i; 434 } 435 return -1; 436 } 437 438 private int getTextSizeFromTheme(int style, int defValue) { 439 TypedArray array = mContext.getTheme().obtainStyledAttributes( 440 style, new int[] { android.R.attr.textSize }); 441 int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); 442 return textSize; 443 } 444} 445