1 /* 2 * Copyright (C) 2008-2009 Google Inc. 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.latin; 18 19 import java.util.List; 20 import java.util.Locale; 21 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.content.res.XmlResourceParser; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.ColorFilter; 29 import android.graphics.Paint; 30 import android.graphics.PixelFormat; 31 import android.graphics.PorterDuff; 32 import android.graphics.Rect; 33 import android.graphics.Paint.Align; 34 import android.graphics.drawable.BitmapDrawable; 35 import android.graphics.drawable.Drawable; 36 import android.inputmethodservice.Keyboard; 37 import android.text.TextPaint; 38 import android.util.Log; 39 import android.view.ViewConfiguration; 40 import android.view.inputmethod.EditorInfo; 41 42 public class LatinKeyboard extends Keyboard { 43 44 private static final boolean DEBUG_PREFERRED_LETTER = false; 45 private static final String TAG = "LatinKeyboard"; 46 47 private Drawable mShiftLockIcon; 48 private Drawable mShiftLockPreviewIcon; 49 private Drawable mOldShiftIcon; 50 private Drawable mOldShiftPreviewIcon; 51 private Drawable mSpaceIcon; 52 private Drawable mSpacePreviewIcon; 53 private Drawable mMicIcon; 54 private Drawable mMicPreviewIcon; 55 private Drawable m123MicIcon; 56 private Drawable m123MicPreviewIcon; 57 private Drawable mButtonArrowLeftIcon; 58 private Drawable mButtonArrowRightIcon; 59 private Key mShiftKey; 60 private Key mEnterKey; 61 private Key mF1Key; 62 private Key mSpaceKey; 63 private Key m123Key; 64 private int mSpaceKeyIndex = -1; 65 private int mSpaceDragStartX; 66 private int mSpaceDragLastDiff; 67 /* package */ Locale mLocale; 68 private LanguageSwitcher mLanguageSwitcher; 69 private Resources mRes; 70 private Context mContext; 71 private int mMode; 72 // Whether this keyboard has voice icon on it 73 private boolean mHasVoiceButton; 74 // Whether voice icon is enabled at all 75 private boolean mVoiceEnabled; 76 private boolean mIsAlphaKeyboard; 77 private CharSequence m123Label; 78 private boolean mCurrentlyInSpace; 79 private SlidingLocaleDrawable mSlidingLocaleIcon; 80 private Rect mBounds = new Rect(); 81 private int[] mPrefLetterFrequencies; 82 private boolean mPreemptiveCorrection; 83 private int mPrefLetter; 84 private int mPrefLetterX; 85 private int mPrefLetterY; 86 private int mPrefDistance; 87 88 private int mExtensionResId; 89 90 private static final int SHIFT_OFF = 0; 91 private static final int SHIFT_ON = 1; 92 private static final int SHIFT_LOCKED = 2; 93 94 private int mShiftState = SHIFT_OFF; 95 96 private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; 97 private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; 98 private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; 99 100 static int sSpacebarVerticalCorrection; 101 102 public LatinKeyboard(Context context, int xmlLayoutResId) { 103 this(context, xmlLayoutResId, 0); 104 } 105 106 public LatinKeyboard(Context context, int xmlLayoutResId, int mode) { 107 super(context, xmlLayoutResId, mode); 108 final Resources res = context.getResources(); 109 mContext = context; 110 mMode = mode; 111 mRes = res; 112 mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked); 113 mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked); 114 mShiftLockPreviewIcon.setBounds(0, 0, 115 mShiftLockPreviewIcon.getIntrinsicWidth(), 116 mShiftLockPreviewIcon.getIntrinsicHeight()); 117 mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space); 118 mSpacePreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_space); 119 mMicIcon = res.getDrawable(R.drawable.sym_keyboard_mic); 120 mMicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_mic); 121 setDefaultBounds(mMicPreviewIcon); 122 mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left); 123 mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right); 124 m123MicIcon = res.getDrawable(R.drawable.sym_keyboard_123_mic); 125 m123MicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_123_mic); 126 setDefaultBounds(m123MicPreviewIcon); 127 sSpacebarVerticalCorrection = res.getDimensionPixelOffset( 128 R.dimen.spacebar_vertical_correction); 129 mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty; 130 mSpaceKeyIndex = indexOf((int) ' '); 131 } 132 133 public LatinKeyboard(Context context, int layoutTemplateResId, 134 CharSequence characters, int columns, int horizontalPadding) { 135 super(context, layoutTemplateResId, characters, columns, horizontalPadding); 136 } 137 138 @Override 139 protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 140 XmlResourceParser parser) { 141 Key key = new LatinKey(res, parent, x, y, parser); 142 switch (key.codes[0]) { 143 case 10: 144 mEnterKey = key; 145 break; 146 case LatinKeyboardView.KEYCODE_F1: 147 mF1Key = key; 148 break; 149 case 32: 150 mSpaceKey = key; 151 break; 152 case KEYCODE_MODE_CHANGE: 153 m123Key = key; 154 m123Label = key.label; 155 break; 156 } 157 return key; 158 } 159 160 void setImeOptions(Resources res, int mode, int options) { 161 if (mEnterKey != null) { 162 // Reset some of the rarely used attributes. 163 mEnterKey.popupCharacters = null; 164 mEnterKey.popupResId = 0; 165 mEnterKey.text = null; 166 switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { 167 case EditorInfo.IME_ACTION_GO: 168 mEnterKey.iconPreview = null; 169 mEnterKey.icon = null; 170 mEnterKey.label = res.getText(R.string.label_go_key); 171 break; 172 case EditorInfo.IME_ACTION_NEXT: 173 mEnterKey.iconPreview = null; 174 mEnterKey.icon = null; 175 mEnterKey.label = res.getText(R.string.label_next_key); 176 break; 177 case EditorInfo.IME_ACTION_DONE: 178 mEnterKey.iconPreview = null; 179 mEnterKey.icon = null; 180 mEnterKey.label = res.getText(R.string.label_done_key); 181 break; 182 case EditorInfo.IME_ACTION_SEARCH: 183 mEnterKey.iconPreview = res.getDrawable( 184 R.drawable.sym_keyboard_feedback_search); 185 mEnterKey.icon = res.getDrawable( 186 R.drawable.sym_keyboard_search); 187 mEnterKey.label = null; 188 break; 189 case EditorInfo.IME_ACTION_SEND: 190 mEnterKey.iconPreview = null; 191 mEnterKey.icon = null; 192 mEnterKey.label = res.getText(R.string.label_send_key); 193 break; 194 default: 195 if (mode == KeyboardSwitcher.MODE_IM) { 196 mEnterKey.icon = null; 197 mEnterKey.iconPreview = null; 198 mEnterKey.label = ":-)"; 199 mEnterKey.text = ":-) "; 200 mEnterKey.popupResId = R.xml.popup_smileys; 201 } else { 202 mEnterKey.iconPreview = res.getDrawable( 203 R.drawable.sym_keyboard_feedback_return); 204 mEnterKey.icon = res.getDrawable( 205 R.drawable.sym_keyboard_return); 206 mEnterKey.label = null; 207 } 208 break; 209 } 210 // Set the initial size of the preview icon 211 if (mEnterKey.iconPreview != null) { 212 mEnterKey.iconPreview.setBounds(0, 0, 213 mEnterKey.iconPreview.getIntrinsicWidth(), 214 mEnterKey.iconPreview.getIntrinsicHeight()); 215 } 216 } 217 } 218 219 void enableShiftLock() { 220 int index = getShiftKeyIndex(); 221 if (index >= 0) { 222 mShiftKey = getKeys().get(index); 223 if (mShiftKey instanceof LatinKey) { 224 ((LatinKey)mShiftKey).enableShiftLock(); 225 } 226 mOldShiftIcon = mShiftKey.icon; 227 mOldShiftPreviewIcon = mShiftKey.iconPreview; 228 } 229 } 230 231 void setShiftLocked(boolean shiftLocked) { 232 if (mShiftKey != null) { 233 if (shiftLocked) { 234 mShiftKey.on = true; 235 mShiftKey.icon = mShiftLockIcon; 236 mShiftState = SHIFT_LOCKED; 237 } else { 238 mShiftKey.on = false; 239 mShiftKey.icon = mShiftLockIcon; 240 mShiftState = SHIFT_ON; 241 } 242 } 243 } 244 245 boolean isShiftLocked() { 246 return mShiftState == SHIFT_LOCKED; 247 } 248 249 @Override 250 public boolean setShifted(boolean shiftState) { 251 boolean shiftChanged = false; 252 if (mShiftKey != null) { 253 if (shiftState == false) { 254 shiftChanged = mShiftState != SHIFT_OFF; 255 mShiftState = SHIFT_OFF; 256 mShiftKey.on = false; 257 mShiftKey.icon = mOldShiftIcon; 258 } else { 259 if (mShiftState == SHIFT_OFF) { 260 shiftChanged = mShiftState == SHIFT_OFF; 261 mShiftState = SHIFT_ON; 262 mShiftKey.icon = mShiftLockIcon; 263 } 264 } 265 } else { 266 return super.setShifted(shiftState); 267 } 268 return shiftChanged; 269 } 270 271 @Override 272 public boolean isShifted() { 273 if (mShiftKey != null) { 274 return mShiftState != SHIFT_OFF; 275 } else { 276 return super.isShifted(); 277 } 278 } 279 280 public void setExtension(int resId) { 281 mExtensionResId = resId; 282 } 283 284 public int getExtension() { 285 return mExtensionResId; 286 } 287 288 private void setDefaultBounds(Drawable drawable) { 289 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 290 } 291 292 public void setVoiceMode(boolean hasVoiceButton, boolean hasVoice) { 293 mHasVoiceButton = hasVoiceButton; 294 mVoiceEnabled = hasVoice; 295 updateF1Key(); 296 } 297 298 private void updateF1Key() { 299 if (mF1Key == null) return; 300 if (m123Key != null && mIsAlphaKeyboard) { 301 if (mVoiceEnabled && !mHasVoiceButton) { 302 m123Key.icon = m123MicIcon; 303 m123Key.iconPreview = m123MicPreviewIcon; 304 m123Key.label = null; 305 } else { 306 m123Key.icon = null; 307 m123Key.iconPreview = null; 308 m123Key.label = m123Label; 309 } 310 } 311 312 if (mHasVoiceButton && mVoiceEnabled) { 313 mF1Key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE }; 314 mF1Key.label = null; 315 mF1Key.icon = mMicIcon; 316 mF1Key.iconPreview = mMicPreviewIcon; 317 } else { 318 mF1Key.label = ","; 319 mF1Key.codes = new int[] { ',' }; 320 mF1Key.icon = null; 321 mF1Key.iconPreview = null; 322 } 323 } 324 325 private void updateSpaceBarForLocale() { 326 if (mLocale != null) { 327 // Create the graphic for spacebar 328 Bitmap buffer = Bitmap.createBitmap(mSpaceKey.width, mSpaceIcon.getIntrinsicHeight(), 329 Bitmap.Config.ARGB_8888); 330 Canvas canvas = new Canvas(buffer); 331 drawSpaceBar(canvas, buffer.getWidth(), buffer.getHeight(), 255); 332 mSpaceKey.icon = new BitmapDrawable(mRes, buffer); 333 mSpaceKey.repeatable = mLanguageSwitcher.getLocaleCount() < 2; 334 } else { 335 mSpaceKey.icon = mRes.getDrawable(R.drawable.sym_keyboard_space); 336 mSpaceKey.repeatable = true; 337 } 338 } 339 340 private void drawSpaceBar(Canvas canvas, int width, int height, int opacity) { 341 canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); 342 Paint paint = new Paint(); 343 paint.setAntiAlias(true); 344 paint.setAlpha(opacity); 345 // Get the text size from the theme 346 paint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14)); 347 paint.setTextAlign(Align.CENTER); 348 //// Draw a drop shadow for the text 349 //paint.setShadowLayer(2f, 0, 0, 0xFF000000); 350 final String language = getInputLanguage(mSpaceKey.width, paint); 351 final int ascent = (int) -paint.ascent(); 352 paint.setColor(0x80000000); 353 canvas.drawText(language, 354 width / 2, ascent - 1, paint); 355 paint.setColor(0xFF808080); 356 canvas.drawText(language, 357 width / 2, ascent, paint); 358 // Put arrows on either side of the text 359 if (mLanguageSwitcher.getLocaleCount() > 1) { 360 Rect bounds = new Rect(); 361 paint.getTextBounds(language, 0, language.length(), bounds); 362 drawButtonArrow(mButtonArrowLeftIcon, canvas, 363 (mSpaceKey.width - bounds.right) / 2 364 - mButtonArrowLeftIcon.getIntrinsicWidth(), 365 (int) paint.getTextSize()); 366 drawButtonArrow(mButtonArrowRightIcon, canvas, 367 (mSpaceKey.width + bounds.right) / 2, (int) paint.getTextSize()); 368 } 369 // Draw the spacebar icon at the bottom 370 int x = (width - mSpaceIcon.getIntrinsicWidth()) / 2; 371 int y = height - mSpaceIcon.getIntrinsicHeight(); 372 mSpaceIcon.setBounds(x, y, 373 x + mSpaceIcon.getIntrinsicWidth(), y + mSpaceIcon.getIntrinsicHeight()); 374 mSpaceIcon.draw(canvas); 375 } 376 377 private void drawButtonArrow(Drawable arrow, Canvas canvas, int x, int bottomY) { 378 arrow.setBounds(x, bottomY - arrow.getIntrinsicHeight(), x + arrow.getIntrinsicWidth(), 379 bottomY); 380 arrow.draw(canvas); 381 } 382 383 private String getInputLanguage(int widthAvail, Paint paint) { 384 return chooseDisplayName(mLanguageSwitcher.getInputLocale(), widthAvail, paint); 385 } 386 387 private String getNextInputLanguage(int widthAvail, Paint paint) { 388 return chooseDisplayName(mLanguageSwitcher.getNextInputLocale(), widthAvail, paint); 389 } 390 391 private String getPrevInputLanguage(int widthAvail, Paint paint) { 392 return chooseDisplayName(mLanguageSwitcher.getPrevInputLocale(), widthAvail, paint); 393 } 394 395 private String chooseDisplayName(Locale locale, int widthAvail, Paint paint) { 396 if (widthAvail < (int) (.35 * getMinWidth())) { 397 return LanguageSwitcher.toTitleCase(locale.getLanguage().substring(0, 2)); 398 } else { 399 return LanguageSwitcher.toTitleCase(locale.getDisplayLanguage(locale)); 400 } 401 } 402 403 private void updateLocaleDrag(int diff) { 404 if (mSlidingLocaleIcon == null) { 405 mSlidingLocaleIcon = new SlidingLocaleDrawable(mSpacePreviewIcon, mSpaceKey.width, 406 mSpacePreviewIcon.getIntrinsicHeight()); 407 mSlidingLocaleIcon.setBounds(0, 0, mSpaceKey.width, 408 mSpacePreviewIcon.getIntrinsicHeight()); 409 mSpaceKey.iconPreview = mSlidingLocaleIcon; 410 } 411 mSlidingLocaleIcon.setDiff(diff); 412 if (Math.abs(diff) == Integer.MAX_VALUE) { 413 mSpaceKey.iconPreview = mSpacePreviewIcon; 414 } else { 415 mSpaceKey.iconPreview = mSlidingLocaleIcon; 416 } 417 mSpaceKey.iconPreview.invalidateSelf(); 418 } 419 420 public int getLanguageChangeDirection() { 421 if (mSpaceKey == null || mLanguageSwitcher.getLocaleCount() < 2 422 || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD ) { 423 return 0; // No change 424 } 425 return mSpaceDragLastDiff > 0 ? 1 : -1; 426 } 427 428 public void setLanguageSwitcher(LanguageSwitcher switcher) { 429 mLanguageSwitcher = switcher; 430 Locale locale = mLanguageSwitcher.getLocaleCount() > 0 431 ? mLanguageSwitcher.getInputLocale() 432 : null; 433 // If the language count is 1 and is the same as the system language, don't show it. 434 if (locale != null 435 && mLanguageSwitcher.getLocaleCount() == 1 436 && mLanguageSwitcher.getSystemLocale().getLanguage() 437 .equalsIgnoreCase(locale.getLanguage())) { 438 locale = null; 439 } 440 if (mLocale != null && mLocale.equals(locale)) return; 441 mLocale = locale; 442 updateSpaceBarForLocale(); 443 } 444 445 boolean isCurrentlyInSpace() { 446 return mCurrentlyInSpace; 447 } 448 449 void setPreferredLetters(int[] frequencies) { 450 mPrefLetterFrequencies = frequencies; 451 mPrefLetter = 0; 452 } 453 454 void keyReleased() { 455 mCurrentlyInSpace = false; 456 mSpaceDragLastDiff = 0; 457 mPrefLetter = 0; 458 mPrefLetterX = 0; 459 mPrefLetterY = 0; 460 mPrefDistance = Integer.MAX_VALUE; 461 if (mSpaceKey != null) { 462 updateLocaleDrag(Integer.MAX_VALUE); 463 } 464 } 465 466 /** 467 * Does the magic of locking the touch gesture into the spacebar when 468 * switching input languages. 469 */ 470 boolean isInside(LatinKey key, int x, int y) { 471 final int code = key.codes[0]; 472 if (code == KEYCODE_SHIFT || 473 code == KEYCODE_DELETE) { 474 y -= key.height / 10; 475 if (code == KEYCODE_SHIFT) x += key.width / 6; 476 if (code == KEYCODE_DELETE) x -= key.width / 6; 477 } else if (code == LatinIME.KEYCODE_SPACE) { 478 y += LatinKeyboard.sSpacebarVerticalCorrection; 479 if (mLanguageSwitcher.getLocaleCount() > 1) { 480 if (mCurrentlyInSpace) { 481 int diff = x - mSpaceDragStartX; 482 if (Math.abs(diff - mSpaceDragLastDiff) > 0) { 483 updateLocaleDrag(diff); 484 } 485 mSpaceDragLastDiff = diff; 486 return true; 487 } else { 488 boolean insideSpace = key.isInsideSuper(x, y); 489 if (insideSpace) { 490 mCurrentlyInSpace = true; 491 mSpaceDragStartX = x; 492 updateLocaleDrag(0); 493 } 494 return insideSpace; 495 } 496 } 497 } else if (mPrefLetterFrequencies != null) { 498 // New coordinate? Reset 499 if (mPrefLetterX != x || mPrefLetterY != y) { 500 mPrefLetter = 0; 501 mPrefDistance = Integer.MAX_VALUE; 502 } 503 // Handle preferred next letter 504 final int[] pref = mPrefLetterFrequencies; 505 if (mPrefLetter > 0) { 506 if (DEBUG_PREFERRED_LETTER && mPrefLetter == code 507 && !key.isInsideSuper(x, y)) { 508 Log.d(TAG, "CORRECTED !!!!!!"); 509 } 510 return mPrefLetter == code; 511 } else { 512 final boolean inside = key.isInsideSuper(x, y); 513 int[] nearby = getNearestKeys(x, y); 514 List<Key> nearbyKeys = getKeys(); 515 if (inside) { 516 // If it's a preferred letter 517 if (inPrefList(code, pref)) { 518 // Check if its frequency is much lower than a nearby key 519 mPrefLetter = code; 520 mPrefLetterX = x; 521 mPrefLetterY = y; 522 for (int i = 0; i < nearby.length; i++) { 523 Key k = nearbyKeys.get(nearby[i]); 524 if (k != key && inPrefList(k.codes[0], pref)) { 525 final int dist = distanceFrom(k, x, y); 526 if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) && 527 (pref[k.codes[0]] > pref[mPrefLetter] * 3)) { 528 mPrefLetter = k.codes[0]; 529 mPrefDistance = dist; 530 if (DEBUG_PREFERRED_LETTER) { 531 Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); 532 } 533 break; 534 } 535 } 536 } 537 538 return mPrefLetter == code; 539 } 540 } 541 542 // Get the surrounding keys and intersect with the preferred list 543 // For all in the intersection 544 // if distance from touch point is within a reasonable distance 545 // make this the pref letter 546 // If no pref letter 547 // return inside; 548 // else return thiskey == prefletter; 549 550 for (int i = 0; i < nearby.length; i++) { 551 Key k = nearbyKeys.get(nearby[i]); 552 if (inPrefList(k.codes[0], pref)) { 553 final int dist = distanceFrom(k, x, y); 554 if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB) 555 && dist < mPrefDistance) { 556 mPrefLetter = k.codes[0]; 557 mPrefLetterX = x; 558 mPrefLetterY = y; 559 mPrefDistance = dist; 560 } 561 } 562 } 563 // Didn't find any 564 if (mPrefLetter == 0) { 565 return inside; 566 } else { 567 return mPrefLetter == code; 568 } 569 } 570 } 571 572 // Lock into the spacebar 573 if (mCurrentlyInSpace) return false; 574 575 return key.isInsideSuper(x, y); 576 } 577 578 private boolean inPrefList(int code, int[] pref) { 579 if (code < pref.length && code >= 0) return pref[code] > 0; 580 return false; 581 } 582 583 private int distanceFrom(Key k, int x, int y) { 584 if (y > k.y && y < k.y + k.height) { 585 return Math.abs(k.x + k.width / 2 - x); 586 } else { 587 return Integer.MAX_VALUE; 588 } 589 } 590 591 @Override 592 public int[] getNearestKeys(int x, int y) { 593 if (mCurrentlyInSpace) { 594 return new int[] { mSpaceKeyIndex }; 595 } else { 596 return super.getNearestKeys(x, y); 597 } 598 } 599 600 private int indexOf(int code) { 601 List<Key> keys = getKeys(); 602 int count = keys.size(); 603 for (int i = 0; i < count; i++) { 604 if (keys.get(i).codes[0] == code) return i; 605 } 606 return -1; 607 } 608 609 private int getTextSizeFromTheme(int style, int defValue) { 610 TypedArray array = mContext.getTheme().obtainStyledAttributes( 611 style, new int[] { android.R.attr.textSize }); 612 int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); 613 return textSize; 614 } 615 616 class LatinKey extends Keyboard.Key { 617 618 private boolean mShiftLockEnabled; 619 620 public LatinKey(Resources res, Keyboard.Row parent, int x, int y, 621 XmlResourceParser parser) { 622 super(res, parent, x, y, parser); 623 if (popupCharacters != null && popupCharacters.length() == 0) { 624 // If there is a keyboard with no keys specified in popupCharacters 625 popupResId = 0; 626 } 627 } 628 629 void enableShiftLock() { 630 mShiftLockEnabled = true; 631 } 632 633 @Override 634 public void onReleased(boolean inside) { 635 if (!mShiftLockEnabled) { 636 super.onReleased(inside); 637 } else { 638 pressed = !pressed; 639 } 640 } 641 642 /** 643 * Overriding this method so that we can reduce the target area for certain keys. 644 */ 645 @Override 646 public boolean isInside(int x, int y) { 647 boolean result = LatinKeyboard.this.isInside(this, x, y); 648 return result; 649 } 650 651 boolean isInsideSuper(int x, int y) { 652 return super.isInside(x, y); 653 } 654 } 655 656 /** 657 * Animation to be displayed on the spacebar preview popup when switching 658 * languages by swiping the spacebar. It draws the current, previous and 659 * next languages and moves them by the delta of touch movement on the spacebar. 660 */ 661 class SlidingLocaleDrawable extends Drawable { 662 663 private int mWidth; 664 private int mHeight; 665 private Drawable mBackground; 666 private int mDiff; 667 private TextPaint mTextPaint; 668 private int mMiddleX; 669 private int mAscent; 670 private Drawable mLeftDrawable; 671 private Drawable mRightDrawable; 672 private boolean mHitThreshold; 673 private int mThreshold; 674 private String mCurrentLanguage; 675 private String mNextLanguage; 676 private String mPrevLanguage; 677 678 public SlidingLocaleDrawable(Drawable background, int width, int height) { 679 mBackground = background; 680 mBackground.setBounds(0, 0, 681 mBackground.getIntrinsicWidth(), mBackground.getIntrinsicHeight()); 682 mWidth = width; 683 mHeight = height; 684 mTextPaint = new TextPaint(); 685 int textSize = getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18); 686 mTextPaint.setTextSize(textSize); 687 mTextPaint.setColor(0); 688 mTextPaint.setTextAlign(Align.CENTER); 689 mTextPaint.setAlpha(255); 690 mTextPaint.setAntiAlias(true); 691 mAscent = (int) mTextPaint.ascent(); 692 mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2; 693 mLeftDrawable = 694 mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_left); 695 mRightDrawable = 696 mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_right); 697 mLeftDrawable.setBounds(0, 0, 698 mLeftDrawable.getIntrinsicWidth(), mLeftDrawable.getIntrinsicHeight()); 699 mRightDrawable.setBounds(mWidth - mRightDrawable.getIntrinsicWidth(), 0, 700 mWidth, mRightDrawable.getIntrinsicHeight()); 701 mThreshold = ViewConfiguration.get(mContext).getScaledTouchSlop(); 702 } 703 704 void setDiff(int diff) { 705 if (diff == Integer.MAX_VALUE) { 706 mHitThreshold = false; 707 mCurrentLanguage = null; 708 return; 709 } 710 mDiff = diff; 711 if (mDiff > mWidth) mDiff = mWidth; 712 if (mDiff < -mWidth) mDiff = -mWidth; 713 if (Math.abs(mDiff) > mThreshold) mHitThreshold = true; 714 invalidateSelf(); 715 } 716 717 @Override 718 public void draw(Canvas canvas) { 719 canvas.save(); 720 if (mHitThreshold) { 721 mTextPaint.setColor(0xFF000000); 722 canvas.clipRect(0, 0, mWidth, mHeight); 723 if (mCurrentLanguage == null) { 724 mCurrentLanguage = getInputLanguage(mWidth, mTextPaint); 725 mNextLanguage = getNextInputLanguage(mWidth, mTextPaint); 726 mPrevLanguage = getPrevInputLanguage(mWidth, mTextPaint); 727 } 728 canvas.drawText(mCurrentLanguage, 729 mWidth / 2 + mDiff, -mAscent + 4, mTextPaint); 730 canvas.drawText(mNextLanguage, 731 mDiff - mWidth / 2, -mAscent + 4, mTextPaint); 732 canvas.drawText(mPrevLanguage, 733 mDiff + mWidth + mWidth / 2, -mAscent + 4, mTextPaint); 734 mLeftDrawable.draw(canvas); 735 mRightDrawable.draw(canvas); 736 } 737 if (mBackground != null) { 738 canvas.translate(mMiddleX, 0); 739 mBackground.draw(canvas); 740 } 741 canvas.restore(); 742 } 743 744 @Override 745 public int getOpacity() { 746 return PixelFormat.TRANSLUCENT; 747 } 748 749 @Override 750 public void setAlpha(int alpha) { 751 // Ignore 752 } 753 754 @Override 755 public void setColorFilter(ColorFilter cf) { 756 // Ignore 757 } 758 759 @Override 760 public int getIntrinsicWidth() { 761 return mWidth; 762 } 763 764 @Override 765 public int getIntrinsicHeight() { 766 return mHeight; 767 } 768 } 769 } 770