1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.pinyin; 18 19 import com.android.inputmethod.pinyin.SoftKeyboard.KeyRow; 20 21 import java.util.List; 22 23 import android.content.Context; 24 import android.graphics.Canvas; 25 import android.graphics.Paint; 26 import android.graphics.Rect; 27 import android.graphics.Paint.FontMetricsInt; 28 import android.graphics.drawable.Drawable; 29 import android.os.Vibrator; 30 import android.util.AttributeSet; 31 import android.view.View; 32 33 /** 34 * Class used to show a soft keyboard. 35 * 36 * A soft keyboard view should not handle touch event itself, because we do bias 37 * correction, need a global strategy to map an event into a proper view to 38 * achieve better user experience. 39 */ 40 public class SoftKeyboardView extends View { 41 /** 42 * The definition of the soft keyboard for the current this soft keyboard 43 * view. 44 */ 45 private SoftKeyboard mSoftKeyboard; 46 47 /** 48 * The popup balloon hint for key press/release. 49 */ 50 private BalloonHint mBalloonPopup; 51 52 /** 53 * The on-key balloon hint for key press/release. If it is null, on-key 54 * highlight will be drawn on th soft keyboard view directly. 55 */ 56 private BalloonHint mBalloonOnKey; 57 58 /** Used to play key sounds. */ 59 private SoundManager mSoundManager; 60 61 /** The last key pressed. */ 62 private SoftKey mSoftKeyDown; 63 64 /** Used to indicate whether the user is holding on a key. */ 65 private boolean mKeyPressed = false; 66 67 /** 68 * The location offset of the view to the keyboard container. 69 */ 70 private int mOffsetToSkbContainer[] = new int[2]; 71 72 /** 73 * The location of the desired hint view to the keyboard container. 74 */ 75 private int mHintLocationToSkbContainer[] = new int[2]; 76 77 /** 78 * Text size for normal key. 79 */ 80 private int mNormalKeyTextSize; 81 82 /** 83 * Text size for function key. 84 */ 85 private int mFunctionKeyTextSize; 86 87 /** 88 * Long press timer used to response long-press. 89 */ 90 private SkbContainer.LongPressTimer mLongPressTimer; 91 92 /** 93 * Repeated events for long press 94 */ 95 private boolean mRepeatForLongPress = false; 96 97 /** 98 * If this parameter is true, the balloon will never be dismissed even if 99 * user moves a lot from the pressed point. 100 */ 101 private boolean mMovingNeverHidePopupBalloon = false; 102 103 /** Vibration for key press. */ 104 private Vibrator mVibrator; 105 106 /** Vibration pattern for key press. */ 107 protected long[] mVibratePattern = new long[] {1, 20}; 108 109 /** 110 * The dirty rectangle used to mark the area to re-draw during key press and 111 * release. Currently, whenever we can invalidate(Rect), view will call 112 * onDraw() and we MUST draw the whole view. This dirty information is for 113 * future use. 114 */ 115 private Rect mDirtyRect = new Rect(); 116 117 private Paint mPaint; 118 private FontMetricsInt mFmi; 119 private boolean mDimSkb; 120 121 public SoftKeyboardView(Context context, AttributeSet attrs) { 122 super(context, attrs); 123 124 mSoundManager = SoundManager.getInstance(mContext); 125 126 mPaint = new Paint(); 127 mPaint.setAntiAlias(true); 128 mFmi = mPaint.getFontMetricsInt(); 129 } 130 131 public boolean setSoftKeyboard(SoftKeyboard softSkb) { 132 if (null == softSkb) { 133 return false; 134 } 135 mSoftKeyboard = softSkb; 136 Drawable bg = softSkb.getSkbBackground(); 137 if (null != bg) setBackgroundDrawable(bg); 138 return true; 139 } 140 141 public SoftKeyboard getSoftKeyboard() { 142 return mSoftKeyboard; 143 } 144 145 public void resizeKeyboard(int skbWidth, int skbHeight) { 146 mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight); 147 } 148 149 public void setBalloonHint(BalloonHint balloonOnKey, 150 BalloonHint balloonPopup, boolean movingNeverHidePopup) { 151 mBalloonOnKey = balloonOnKey; 152 mBalloonPopup = balloonPopup; 153 mMovingNeverHidePopupBalloon = movingNeverHidePopup; 154 } 155 156 public void setOffsetToSkbContainer(int offsetToSkbContainer[]) { 157 mOffsetToSkbContainer[0] = offsetToSkbContainer[0]; 158 mOffsetToSkbContainer[1] = offsetToSkbContainer[1]; 159 } 160 161 @Override 162 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 163 int measuredWidth = 0; 164 int measuredHeight = 0; 165 if (null != mSoftKeyboard) { 166 measuredWidth = mSoftKeyboard.getSkbCoreWidth(); 167 measuredHeight = mSoftKeyboard.getSkbCoreHeight(); 168 measuredWidth += mPaddingLeft + mPaddingRight; 169 measuredHeight += mPaddingTop + mPaddingBottom; 170 } 171 setMeasuredDimension(measuredWidth, measuredHeight); 172 } 173 174 private void showBalloon(BalloonHint balloon, int balloonLocationToSkb[], 175 boolean movePress) { 176 long delay = BalloonHint.TIME_DELAY_SHOW; 177 if (movePress) delay = 0; 178 if (balloon.needForceDismiss()) { 179 balloon.delayedDismiss(0); 180 } 181 if (!balloon.isShowing()) { 182 balloon.delayedShow(delay, balloonLocationToSkb); 183 } else { 184 balloon.delayedUpdate(delay, balloonLocationToSkb, balloon 185 .getWidth(), balloon.getHeight()); 186 } 187 long b = System.currentTimeMillis(); 188 } 189 190 public void resetKeyPress(long balloonDelay) { 191 if (!mKeyPressed) return; 192 mKeyPressed = false; 193 if (null != mBalloonOnKey) { 194 mBalloonOnKey.delayedDismiss(balloonDelay); 195 } else { 196 if (null != mSoftKeyDown) { 197 if (mDirtyRect.isEmpty()) { 198 mDirtyRect.set(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, 199 mSoftKeyDown.mRight, mSoftKeyDown.mBottom); 200 } 201 invalidate(mDirtyRect); 202 } else { 203 invalidate(); 204 } 205 } 206 mBalloonPopup.delayedDismiss(balloonDelay); 207 } 208 209 // If movePress is true, means that this function is called because user 210 // moves his finger to this button. If movePress is false, means that this 211 // function is called when user just presses this key. 212 public SoftKey onKeyPress(int x, int y, 213 SkbContainer.LongPressTimer longPressTimer, boolean movePress) { 214 mKeyPressed = false; 215 boolean moveWithinPreviousKey = false; 216 if (movePress) { 217 SoftKey newKey = mSoftKeyboard.mapToKey(x, y); 218 if (newKey == mSoftKeyDown) moveWithinPreviousKey = true; 219 mSoftKeyDown = newKey; 220 } else { 221 mSoftKeyDown = mSoftKeyboard.mapToKey(x, y); 222 } 223 if (moveWithinPreviousKey || null == mSoftKeyDown) return mSoftKeyDown; 224 mKeyPressed = true; 225 226 if (!movePress) { 227 tryPlayKeyDown(); 228 tryVibrate(); 229 } 230 231 mLongPressTimer = longPressTimer; 232 233 if (!movePress) { 234 if (mSoftKeyDown.getPopupResId() > 0 || mSoftKeyDown.repeatable()) { 235 mLongPressTimer.startTimer(); 236 } 237 } else { 238 mLongPressTimer.removeTimer(); 239 } 240 241 int desired_width; 242 int desired_height; 243 float textSize; 244 Environment env = Environment.getInstance(); 245 246 if (null != mBalloonOnKey) { 247 Drawable keyHlBg = mSoftKeyDown.getKeyHlBg(); 248 mBalloonOnKey.setBalloonBackground(keyHlBg); 249 250 // Prepare the on-key balloon 251 int keyXMargin = mSoftKeyboard.getKeyXMargin(); 252 int keyYMargin = mSoftKeyboard.getKeyYMargin(); 253 desired_width = mSoftKeyDown.width() - 2 * keyXMargin; 254 desired_height = mSoftKeyDown.height() - 2 * keyYMargin; 255 textSize = env 256 .getKeyTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId); 257 Drawable icon = mSoftKeyDown.getKeyIcon(); 258 if (null != icon) { 259 mBalloonOnKey.setBalloonConfig(icon, desired_width, 260 desired_height); 261 } else { 262 mBalloonOnKey.setBalloonConfig(mSoftKeyDown.getKeyLabel(), 263 textSize, true, mSoftKeyDown.getColorHl(), 264 desired_width, desired_height); 265 } 266 267 mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft 268 - (mBalloonOnKey.getWidth() - mSoftKeyDown.width()) / 2; 269 mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0]; 270 mHintLocationToSkbContainer[1] = mPaddingTop 271 + (mSoftKeyDown.mBottom - keyYMargin) 272 - mBalloonOnKey.getHeight(); 273 mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1]; 274 showBalloon(mBalloonOnKey, mHintLocationToSkbContainer, movePress); 275 } else { 276 mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, 277 mSoftKeyDown.mRight, mSoftKeyDown.mBottom); 278 invalidate(mDirtyRect); 279 } 280 281 // Prepare the popup balloon 282 if (mSoftKeyDown.needBalloon()) { 283 Drawable balloonBg = mSoftKeyboard.getBalloonBackground(); 284 mBalloonPopup.setBalloonBackground(balloonBg); 285 286 desired_width = mSoftKeyDown.width() + env.getKeyBalloonWidthPlus(); 287 desired_height = mSoftKeyDown.height() 288 + env.getKeyBalloonHeightPlus(); 289 textSize = env 290 .getBalloonTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId); 291 Drawable iconPopup = mSoftKeyDown.getKeyIconPopup(); 292 if (null != iconPopup) { 293 mBalloonPopup.setBalloonConfig(iconPopup, desired_width, 294 desired_height); 295 } else { 296 mBalloonPopup.setBalloonConfig(mSoftKeyDown.getKeyLabel(), 297 textSize, mSoftKeyDown.needBalloon(), mSoftKeyDown 298 .getColorBalloon(), desired_width, 299 desired_height); 300 } 301 302 // The position to show. 303 mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft 304 + -(mBalloonPopup.getWidth() - mSoftKeyDown.width()) / 2; 305 mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0]; 306 mHintLocationToSkbContainer[1] = mPaddingTop + mSoftKeyDown.mTop 307 - mBalloonPopup.getHeight(); 308 mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1]; 309 showBalloon(mBalloonPopup, mHintLocationToSkbContainer, movePress); 310 } else { 311 mBalloonPopup.delayedDismiss(0); 312 } 313 314 if (mRepeatForLongPress) longPressTimer.startTimer(); 315 return mSoftKeyDown; 316 } 317 318 public SoftKey onKeyRelease(int x, int y) { 319 mKeyPressed = false; 320 if (null == mSoftKeyDown) return null; 321 322 mLongPressTimer.removeTimer(); 323 324 if (null != mBalloonOnKey) { 325 mBalloonOnKey.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS); 326 } else { 327 mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, 328 mSoftKeyDown.mRight, mSoftKeyDown.mBottom); 329 invalidate(mDirtyRect); 330 } 331 332 if (mSoftKeyDown.needBalloon()) { 333 mBalloonPopup.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS); 334 } 335 336 if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) { 337 return mSoftKeyDown; 338 } 339 return null; 340 } 341 342 public SoftKey onKeyMove(int x, int y) { 343 if (null == mSoftKeyDown) return null; 344 345 if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) { 346 return mSoftKeyDown; 347 } 348 349 // The current key needs to be updated. 350 mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, 351 mSoftKeyDown.mRight, mSoftKeyDown.mBottom); 352 353 if (mRepeatForLongPress) { 354 if (mMovingNeverHidePopupBalloon) { 355 return onKeyPress(x, y, mLongPressTimer, true); 356 } 357 358 if (null != mBalloonOnKey) { 359 mBalloonOnKey.delayedDismiss(0); 360 } else { 361 invalidate(mDirtyRect); 362 } 363 364 if (mSoftKeyDown.needBalloon()) { 365 mBalloonPopup.delayedDismiss(0); 366 } 367 368 if (null != mLongPressTimer) { 369 mLongPressTimer.removeTimer(); 370 } 371 return onKeyPress(x, y, mLongPressTimer, true); 372 } else { 373 // When user moves between keys, repeated response is disabled. 374 return onKeyPress(x, y, mLongPressTimer, true); 375 } 376 } 377 378 private void tryVibrate() { 379 if (!Settings.getVibrate()) { 380 return; 381 } 382 if (mVibrator == null) { 383 mVibrator = new Vibrator(); 384 } 385 mVibrator.vibrate(mVibratePattern, -1); 386 } 387 388 private void tryPlayKeyDown() { 389 if (Settings.getKeySound()) { 390 mSoundManager.playKeyDown(); 391 } 392 } 393 394 public void dimSoftKeyboard(boolean dimSkb) { 395 mDimSkb = dimSkb; 396 invalidate(); 397 } 398 399 @Override 400 protected void onDraw(Canvas canvas) { 401 if (null == mSoftKeyboard) return; 402 403 canvas.translate(mPaddingLeft, mPaddingTop); 404 405 Environment env = Environment.getInstance(); 406 mNormalKeyTextSize = env.getKeyTextSize(false); 407 mFunctionKeyTextSize = env.getKeyTextSize(true); 408 // Draw the last soft keyboard 409 int rowNum = mSoftKeyboard.getRowNum(); 410 int keyXMargin = mSoftKeyboard.getKeyXMargin(); 411 int keyYMargin = mSoftKeyboard.getKeyYMargin(); 412 for (int row = 0; row < rowNum; row++) { 413 KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row); 414 if (null == keyRow) continue; 415 List<SoftKey> softKeys = keyRow.mSoftKeys; 416 int keyNum = softKeys.size(); 417 for (int i = 0; i < keyNum; i++) { 418 SoftKey softKey = softKeys.get(i); 419 if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) { 420 mPaint.setTextSize(mNormalKeyTextSize); 421 } else { 422 mPaint.setTextSize(mFunctionKeyTextSize); 423 } 424 drawSoftKey(canvas, softKey, keyXMargin, keyYMargin); 425 } 426 } 427 428 if (mDimSkb) { 429 mPaint.setColor(0xa0000000); 430 canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); 431 } 432 433 mDirtyRect.setEmpty(); 434 } 435 436 private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin, 437 int keyYMargin) { 438 Drawable bg; 439 int textColor; 440 if (mKeyPressed && softKey == mSoftKeyDown) { 441 bg = softKey.getKeyHlBg(); 442 textColor = softKey.getColorHl(); 443 } else { 444 bg = softKey.getKeyBg(); 445 textColor = softKey.getColor(); 446 } 447 448 if (null != bg) { 449 bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin, 450 softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin); 451 bg.draw(canvas); 452 } 453 454 String keyLabel = softKey.getKeyLabel(); 455 Drawable keyIcon = softKey.getKeyIcon(); 456 if (null != keyIcon) { 457 Drawable icon = keyIcon; 458 int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2; 459 int marginRight = softKey.width() - icon.getIntrinsicWidth() 460 - marginLeft; 461 int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2; 462 int marginBottom = softKey.height() - icon.getIntrinsicHeight() 463 - marginTop; 464 icon.setBounds(softKey.mLeft + marginLeft, 465 softKey.mTop + marginTop, softKey.mRight - marginRight, 466 softKey.mBottom - marginBottom); 467 icon.draw(canvas); 468 } else if (null != keyLabel) { 469 mPaint.setColor(textColor); 470 float x = softKey.mLeft 471 + (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f; 472 int fontHeight = mFmi.bottom - mFmi.top; 473 float marginY = (softKey.height() - fontHeight) / 2.0f; 474 float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f; 475 canvas.drawText(keyLabel, x, y + 1, mPaint); 476 } 477 } 478 } 479