• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SoftKeyboardView(Context context, AttributeSet attrs)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 
setSoftKeyboard(SoftKeyboard softSkb)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 
getSoftKeyboard()141     public SoftKeyboard getSoftKeyboard() {
142         return mSoftKeyboard;
143     }
144 
resizeKeyboard(int skbWidth, int skbHeight)145     public void resizeKeyboard(int skbWidth, int skbHeight) {
146         mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight);
147     }
148 
setBalloonHint(BalloonHint balloonOnKey, BalloonHint balloonPopup, boolean movingNeverHidePopup)149     public void setBalloonHint(BalloonHint balloonOnKey,
150             BalloonHint balloonPopup, boolean movingNeverHidePopup) {
151         mBalloonOnKey = balloonOnKey;
152         mBalloonPopup = balloonPopup;
153         mMovingNeverHidePopupBalloon = movingNeverHidePopup;
154     }
155 
setOffsetToSkbContainer(int offsetToSkbContainer[])156     public void setOffsetToSkbContainer(int offsetToSkbContainer[]) {
157         mOffsetToSkbContainer[0] = offsetToSkbContainer[0];
158         mOffsetToSkbContainer[1] = offsetToSkbContainer[1];
159     }
160 
161     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
showBalloon(BalloonHint balloon, int balloonLocationToSkb[], boolean movePress)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 
resetKeyPress(long balloonDelay)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.
onKeyPress(int x, int y, SkbContainer.LongPressTimer longPressTimer, boolean movePress)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 
onKeyRelease(int x, int y)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 
onKeyMove(int x, int y)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 
tryVibrate()378     private void tryVibrate() {
379         if (!Settings.getVibrate()) {
380             return;
381         }
382         if (mVibrator == null) {
383             mVibrator = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
384         }
385         mVibrator.vibrate(mVibratePattern, -1);
386     }
387 
tryPlayKeyDown()388     private void tryPlayKeyDown() {
389         if (Settings.getKeySound()) {
390             mSoundManager.playKeyDown();
391         }
392     }
393 
dimSoftKeyboard(boolean dimSkb)394     public void dimSoftKeyboard(boolean dimSkb) {
395         mDimSkb = dimSkb;
396         invalidate();
397     }
398 
399     @Override
onDraw(Canvas canvas)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 
drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin, int keyYMargin)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