• 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 android.content.Context;
20 import android.content.res.Resources;
21 import android.inputmethodservice.InputMethodService;
22 import android.os.Handler;
23 import android.os.SystemClock;
24 import android.os.SystemProperties;
25 import android.util.AttributeSet;
26 import android.view.GestureDetector;
27 import android.view.Gravity;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.View.OnTouchListener;
31 import android.widget.PopupWindow;
32 import android.widget.RelativeLayout;
33 import android.widget.ViewFlipper;
34 
35 /**
36  * The top container to host soft keyboard view(s).
37  */
38 public class SkbContainer extends RelativeLayout implements OnTouchListener {
39     /**
40      * For finger touch, user tends to press the bottom part of the target key,
41      * or he/she even presses the area out of it, so it is necessary to make a
42      * simple bias correction. If the input method runs on emulator, no bias
43      * correction will be used.
44      */
45     private static final int Y_BIAS_CORRECTION = -10;
46 
47     /**
48      * Used to skip these move events whose position is too close to the
49      * previous touch events.
50      */
51     private static final int MOVE_TOLERANCE = 6;
52 
53     /**
54      * If this member is true, PopupWindow is used to show on-key highlight
55      * effect.
56      */
57     private static boolean POPUPWINDOW_FOR_PRESSED_UI = false;
58 
59     /**
60      * The current soft keyboard layout.
61      *
62      * @see com.android.inputmethod.pinyin.InputModeSwitcher for detailed layout
63      *      definitions.
64      */
65     private int mSkbLayout = 0;
66 
67     /**
68      * The input method service.
69      */
70     private InputMethodService mService;
71 
72     /**
73      * Input mode switcher used to switch between different modes like Chinese,
74      * English, etc.
75      */
76     private InputModeSwitcher mInputModeSwitcher;
77 
78     /**
79      * The gesture detector.
80      */
81     private GestureDetector mGestureDetector;
82 
83     private Environment mEnvironment;
84 
85     private ViewFlipper mSkbFlipper;
86 
87     /**
88      * The popup balloon hint for key press/release.
89      */
90     private BalloonHint mBalloonPopup;
91 
92     /**
93      * The on-key balloon hint for key press/release.
94      */
95     private BalloonHint mBalloonOnKey = null;
96 
97     /** The major sub soft keyboard. */
98     private SoftKeyboardView mMajorView;
99 
100     /**
101      * The last parameter when function {@link #toggleCandidateMode(boolean)}
102      * was called.
103      */
104     private boolean mLastCandidatesShowing;
105 
106     /** Used to indicate whether a popup soft keyboard is shown. */
107     private boolean mPopupSkbShow = false;
108 
109     /**
110      * Used to indicate whether a popup soft keyboard is just shown, and waits
111      * for the touch event to release. After the release, the popup window can
112      * response to touch events.
113      **/
114     private boolean mPopupSkbNoResponse = false;
115 
116     /** Popup sub keyboard. */
117     private PopupWindow mPopupSkb;
118 
119     /** The view of the popup sub soft keyboard. */
120     private SoftKeyboardView mPopupSkbView;
121 
122     private int mPopupX;
123 
124     private int mPopupY;
125 
126     /**
127      * When user presses a key, a timer is started, when it times out, it is
128      * necessary to detect whether user still holds the key.
129      */
130     private volatile boolean mWaitForTouchUp = false;
131 
132     /**
133      * When user drags on the soft keyboard and the distance is enough, this
134      * drag will be recognized as a gesture and a gesture-based action will be
135      * taken, in this situation, ignore the consequent events.
136      */
137     private volatile boolean mDiscardEvent = false;
138 
139     /**
140      * For finger touch, user tends to press the bottom part of the target key,
141      * or he/she even presses the area out of it, so it is necessary to make a
142      * simple bias correction in Y.
143      */
144     private int mYBiasCorrection = 0;
145 
146     /**
147      * The x coordination of the last touch event.
148      */
149     private int mXLast;
150 
151     /**
152      * The y coordination of the last touch event.
153      */
154     private int mYLast;
155 
156     /**
157      * The soft keyboard view.
158      */
159     private SoftKeyboardView mSkv;
160 
161     /**
162      * The position of the soft keyboard view in the container.
163      */
164     private int mSkvPosInContainer[] = new int[2];
165 
166     /**
167      * The key pressed by user.
168      */
169     private SoftKey mSoftKeyDown = null;
170 
171     /**
172      * Used to timeout a press if user holds the key for a long time.
173      */
174     private LongPressTimer mLongPressTimer;
175 
176     /**
177      * For temporary use.
178      */
179     private int mXyPosTmp[] = new int[2];
180 
SkbContainer(Context context, AttributeSet attrs)181     public SkbContainer(Context context, AttributeSet attrs) {
182         super(context, attrs);
183 
184         mEnvironment = Environment.getInstance();
185 
186         mLongPressTimer = new LongPressTimer(this);
187 
188         // If it runs on an emulator, no bias correction
189         if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) {
190             mYBiasCorrection = 0;
191         } else {
192             mYBiasCorrection = Y_BIAS_CORRECTION;
193         }
194         mBalloonPopup = new BalloonHint(context, this, MeasureSpec.AT_MOST);
195         if (POPUPWINDOW_FOR_PRESSED_UI) {
196             mBalloonOnKey = new BalloonHint(context, this, MeasureSpec.AT_MOST);
197         }
198 
199         mPopupSkb = new PopupWindow(mContext);
200         mPopupSkb.setBackgroundDrawable(null);
201         mPopupSkb.setClippingEnabled(false);
202     }
203 
setService(InputMethodService service)204     public void setService(InputMethodService service) {
205         mService = service;
206     }
207 
setInputModeSwitcher(InputModeSwitcher inputModeSwitcher)208     public void setInputModeSwitcher(InputModeSwitcher inputModeSwitcher) {
209         mInputModeSwitcher = inputModeSwitcher;
210     }
211 
setGestureDetector(GestureDetector gestureDetector)212     public void setGestureDetector(GestureDetector gestureDetector) {
213         mGestureDetector = gestureDetector;
214     }
215 
isCurrentSkbSticky()216     public boolean isCurrentSkbSticky() {
217         if (null == mMajorView) return true;
218         SoftKeyboard skb = mMajorView.getSoftKeyboard();
219         if (null != skb) {
220             return skb.getStickyFlag();
221         }
222         return true;
223     }
224 
toggleCandidateMode(boolean candidatesShowing)225     public void toggleCandidateMode(boolean candidatesShowing) {
226         if (null == mMajorView || !mInputModeSwitcher.isChineseText()
227                 || mLastCandidatesShowing == candidatesShowing) return;
228         mLastCandidatesShowing = candidatesShowing;
229 
230         SoftKeyboard skb = mMajorView.getSoftKeyboard();
231         if (null == skb) return;
232 
233         int state = mInputModeSwitcher.getTooggleStateForCnCand();
234         if (!candidatesShowing) {
235             skb.disableToggleState(state, false);
236             skb.enableToggleStates(mInputModeSwitcher.getToggleStates());
237         } else {
238             skb.enableToggleState(state, false);
239         }
240 
241         mMajorView.invalidate();
242     }
243 
updateInputMode()244     public void updateInputMode() {
245         int skbLayout = mInputModeSwitcher.getSkbLayout();
246         if (mSkbLayout != skbLayout) {
247             mSkbLayout = skbLayout;
248             updateSkbLayout();
249         }
250 
251         mLastCandidatesShowing = false;
252 
253         if (null == mMajorView) return;
254 
255         SoftKeyboard skb = mMajorView.getSoftKeyboard();
256         if (null == skb) return;
257         skb.enableToggleStates(mInputModeSwitcher.getToggleStates());
258         invalidate();
259         return;
260     }
261 
updateSkbLayout()262     private void updateSkbLayout() {
263         int screenWidth = mEnvironment.getScreenWidth();
264         int keyHeight = mEnvironment.getKeyHeight();
265         int skbHeight = mEnvironment.getSkbHeight();
266 
267         Resources r = mContext.getResources();
268         if (null == mSkbFlipper) {
269             mSkbFlipper = (ViewFlipper) findViewById(R.id.alpha_floatable);
270         }
271         mMajorView = (SoftKeyboardView) mSkbFlipper.getChildAt(0);
272 
273         SoftKeyboard majorSkb = null;
274         SkbPool skbPool = SkbPool.getInstance();
275 
276         switch (mSkbLayout) {
277         case R.xml.skb_qwerty:
278             majorSkb = skbPool.getSoftKeyboard(R.xml.skb_qwerty,
279                     R.xml.skb_qwerty, screenWidth, skbHeight, mContext);
280             break;
281 
282         case R.xml.skb_sym1:
283             majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1,
284                     screenWidth, skbHeight, mContext);
285             break;
286 
287         case R.xml.skb_sym2:
288             majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym2, R.xml.skb_sym2,
289                     screenWidth, skbHeight, mContext);
290             break;
291 
292         case R.xml.skb_smiley:
293             majorSkb = skbPool.getSoftKeyboard(R.xml.skb_smiley,
294                     R.xml.skb_smiley, screenWidth, skbHeight, mContext);
295             break;
296 
297         case R.xml.skb_phone:
298             majorSkb = skbPool.getSoftKeyboard(R.xml.skb_phone,
299                     R.xml.skb_phone, screenWidth, skbHeight, mContext);
300             break;
301         default:
302         }
303 
304         if (null == majorSkb || !mMajorView.setSoftKeyboard(majorSkb)) {
305             return;
306         }
307         mMajorView.setBalloonHint(mBalloonOnKey, mBalloonPopup, false);
308         mMajorView.invalidate();
309     }
310 
responseKeyEvent(SoftKey sKey)311     private void responseKeyEvent(SoftKey sKey) {
312         if (null == sKey) return;
313         ((PinyinIME) mService).responseSoftKeyEvent(sKey);
314         return;
315     }
316 
inKeyboardView(int x, int y, int positionInParent[])317     private SoftKeyboardView inKeyboardView(int x, int y,
318             int positionInParent[]) {
319         if (mPopupSkbShow) {
320             if (mPopupX <= x && mPopupX + mPopupSkb.getWidth() > x
321                     && mPopupY <= y && mPopupY + mPopupSkb.getHeight() > y) {
322                 positionInParent[0] = mPopupX;
323                 positionInParent[1] = mPopupY;
324                 mPopupSkbView.setOffsetToSkbContainer(positionInParent);
325                 return mPopupSkbView;
326             }
327             return null;
328         }
329 
330         return mMajorView;
331     }
332 
popupSymbols()333     private void popupSymbols() {
334         int popupResId = mSoftKeyDown.getPopupResId();
335         if (popupResId > 0) {
336             int skbContainerWidth = getWidth();
337             int skbContainerHeight = getHeight();
338             // The paddings of the background are not included.
339             int miniSkbWidth = (int) (skbContainerWidth * 0.8);
340             int miniSkbHeight = (int) (skbContainerHeight * 0.23);
341 
342             SkbPool skbPool = SkbPool.getInstance();
343             SoftKeyboard skb = skbPool.getSoftKeyboard(popupResId, popupResId,
344                     miniSkbWidth, miniSkbHeight, mContext);
345             if (null == skb) return;
346 
347             mPopupX = (skbContainerWidth - skb.getSkbTotalWidth()) / 2;
348             mPopupY = (skbContainerHeight - skb.getSkbTotalHeight()) / 2;
349 
350             if (null == mPopupSkbView) {
351                 mPopupSkbView = new SoftKeyboardView(mContext, null);
352                 mPopupSkbView.onMeasure(LayoutParams.WRAP_CONTENT,
353                         LayoutParams.WRAP_CONTENT);
354             }
355             mPopupSkbView.setOnTouchListener(this);
356             mPopupSkbView.setSoftKeyboard(skb);
357             mPopupSkbView.setBalloonHint(mBalloonOnKey, mBalloonPopup, true);
358 
359             mPopupSkb.setContentView(mPopupSkbView);
360             mPopupSkb.setWidth(skb.getSkbCoreWidth()
361                     + mPopupSkbView.getPaddingLeft()
362                     + mPopupSkbView.getPaddingRight());
363             mPopupSkb.setHeight(skb.getSkbCoreHeight()
364                     + mPopupSkbView.getPaddingTop()
365                     + mPopupSkbView.getPaddingBottom());
366 
367             getLocationInWindow(mXyPosTmp);
368             mPopupSkb.showAtLocation(this, Gravity.NO_GRAVITY, mPopupX, mPopupY
369                     + mXyPosTmp[1]);
370             mPopupSkbShow = true;
371             mPopupSkbNoResponse = true;
372             // Invalidate itself to dim the current soft keyboards.
373             dimSoftKeyboard(true);
374             resetKeyPress(0);
375         }
376     }
377 
dimSoftKeyboard(boolean dimSkb)378     private void dimSoftKeyboard(boolean dimSkb) {
379         mMajorView.dimSoftKeyboard(dimSkb);
380     }
381 
dismissPopupSkb()382     private void dismissPopupSkb() {
383         mPopupSkb.dismiss();
384         mPopupSkbShow = false;
385         dimSoftKeyboard(false);
386         resetKeyPress(0);
387     }
388 
resetKeyPress(long delay)389     private void resetKeyPress(long delay) {
390         mLongPressTimer.removeTimer();
391 
392         if (null != mSkv) {
393             mSkv.resetKeyPress(delay);
394         }
395     }
396 
handleBack(boolean realAction)397     public boolean handleBack(boolean realAction) {
398         if (mPopupSkbShow) {
399             if (!realAction) return true;
400 
401             dismissPopupSkb();
402             mDiscardEvent = true;
403             return true;
404         }
405         return false;
406     }
407 
dismissPopups()408     public void dismissPopups() {
409         handleBack(true);
410         resetKeyPress(0);
411     }
412 
413     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)414     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
415         Environment env = Environment.getInstance();
416         int measuredWidth = env.getScreenWidth();
417         int measuredHeight = getPaddingTop();
418         measuredHeight += env.getSkbHeight();
419         widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
420                 MeasureSpec.EXACTLY);
421         heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,
422                 MeasureSpec.EXACTLY);
423         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
424     }
425 
426     @Override
onTouchEvent(MotionEvent event)427     public boolean onTouchEvent(MotionEvent event) {
428         super.onTouchEvent(event);
429 
430         if (mSkbFlipper.isFlipping()) {
431             resetKeyPress(0);
432             return true;
433         }
434 
435         int x = (int) event.getX();
436         int y = (int) event.getY();
437         // Bias correction
438         y = y + mYBiasCorrection;
439 
440         // Ignore short-distance movement event to get better performance.
441         if (event.getAction() == MotionEvent.ACTION_MOVE) {
442             if (Math.abs(x - mXLast) <= MOVE_TOLERANCE
443                     && Math.abs(y - mYLast) <= MOVE_TOLERANCE) {
444                 return true;
445             }
446         }
447 
448         mXLast = x;
449         mYLast = y;
450 
451         if (!mPopupSkbShow) {
452             if (mGestureDetector.onTouchEvent(event)) {
453                 resetKeyPress(0);
454                 mDiscardEvent = true;
455                 return true;
456             }
457         }
458 
459         switch (event.getAction()) {
460         case MotionEvent.ACTION_DOWN:
461             resetKeyPress(0);
462 
463             mWaitForTouchUp = true;
464             mDiscardEvent = false;
465 
466             mSkv = null;
467             mSoftKeyDown = null;
468             mSkv = inKeyboardView(x, y, mSkvPosInContainer);
469             if (null != mSkv) {
470                 mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
471                         - mSkvPosInContainer[1], mLongPressTimer, false);
472             }
473             break;
474 
475         case MotionEvent.ACTION_MOVE:
476             if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
477                 break;
478             }
479             if (mDiscardEvent) {
480                 resetKeyPress(0);
481                 break;
482             }
483 
484             if (mPopupSkbShow && mPopupSkbNoResponse) {
485                 break;
486             }
487 
488             SoftKeyboardView skv = inKeyboardView(x, y, mSkvPosInContainer);
489             if (null != skv) {
490                 if (skv != mSkv) {
491                     mSkv = skv;
492                     mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
493                             - mSkvPosInContainer[1], mLongPressTimer, true);
494                 } else if (null != skv) {
495                     if (null != mSkv) {
496                         mSoftKeyDown = mSkv.onKeyMove(
497                                 x - mSkvPosInContainer[0], y
498                                         - mSkvPosInContainer[1]);
499                         if (null == mSoftKeyDown) {
500                             mDiscardEvent = true;
501                         }
502                     }
503                 }
504             }
505             break;
506 
507         case MotionEvent.ACTION_UP:
508             if (mDiscardEvent) {
509                 resetKeyPress(0);
510                 break;
511             }
512 
513             mWaitForTouchUp = false;
514 
515             // The view which got the {@link MotionEvent#ACTION_DOWN} event is
516             // always used to handle this event.
517             if (null != mSkv) {
518                 mSkv.onKeyRelease(x - mSkvPosInContainer[0], y
519                         - mSkvPosInContainer[1]);
520             }
521 
522             if (!mPopupSkbShow || !mPopupSkbNoResponse) {
523                 responseKeyEvent(mSoftKeyDown);
524             }
525 
526             if (mSkv == mPopupSkbView && !mPopupSkbNoResponse) {
527                 dismissPopupSkb();
528             }
529             mPopupSkbNoResponse = false;
530             break;
531 
532         case MotionEvent.ACTION_CANCEL:
533             break;
534         }
535 
536         if (null == mSkv) {
537             return false;
538         }
539 
540         return true;
541     }
542 
543     // Function for interface OnTouchListener, it is used to handle touch events
544     // which will be delivered to the popup soft keyboard view.
onTouch(View v, MotionEvent event)545     public boolean onTouch(View v, MotionEvent event) {
546         // Translate the event to fit to the container.
547         MotionEvent newEv = MotionEvent.obtain(event.getDownTime(), event
548                 .getEventTime(), event.getAction(), event.getX() + mPopupX,
549                 event.getY() + mPopupY, event.getPressure(), event.getSize(),
550                 event.getMetaState(), event.getXPrecision(), event
551                         .getYPrecision(), event.getDeviceId(), event
552                         .getEdgeFlags());
553         boolean ret = onTouchEvent(newEv);
554         return ret;
555     }
556 
557     class LongPressTimer extends Handler implements Runnable {
558         /**
559          * When user presses a key for a long time, the timeout interval to
560          * generate first {@link #LONG_PRESS_KEYNUM1} key events.
561          */
562         public static final int LONG_PRESS_TIMEOUT1 = 500;
563 
564         /**
565          * When user presses a key for a long time, after the first
566          * {@link #LONG_PRESS_KEYNUM1} key events, this timeout interval will be
567          * used.
568          */
569         private static final int LONG_PRESS_TIMEOUT2 = 100;
570 
571         /**
572          * When user presses a key for a long time, after the first
573          * {@link #LONG_PRESS_KEYNUM2} key events, this timeout interval will be
574          * used.
575          */
576         private static final int LONG_PRESS_TIMEOUT3 = 100;
577 
578         /**
579          * When user presses a key for a long time, after the first
580          * {@link #LONG_PRESS_KEYNUM1} key events, timeout interval
581          * {@link #LONG_PRESS_TIMEOUT2} will be used instead.
582          */
583         public static final int LONG_PRESS_KEYNUM1 = 1;
584 
585         /**
586          * When user presses a key for a long time, after the first
587          * {@link #LONG_PRESS_KEYNUM2} key events, timeout interval
588          * {@link #LONG_PRESS_TIMEOUT3} will be used instead.
589          */
590         public static final int LONG_PRESS_KEYNUM2 = 3;
591 
592         SkbContainer mSkbContainer;
593 
594         private int mResponseTimes = 0;
595 
LongPressTimer(SkbContainer skbContainer)596         public LongPressTimer(SkbContainer skbContainer) {
597             mSkbContainer = skbContainer;
598         }
599 
startTimer()600         public void startTimer() {
601             postAtTime(this, SystemClock.uptimeMillis() + LONG_PRESS_TIMEOUT1);
602             mResponseTimes = 0;
603         }
604 
removeTimer()605         public boolean removeTimer() {
606             removeCallbacks(this);
607             return true;
608         }
609 
run()610         public void run() {
611             if (mWaitForTouchUp) {
612                 mResponseTimes++;
613                 if (mSoftKeyDown.repeatable()) {
614                     if (mSoftKeyDown.isUserDefKey()) {
615                         if (1 == mResponseTimes) {
616                             if (mInputModeSwitcher
617                                     .tryHandleLongPressSwitch(mSoftKeyDown.mKeyCode)) {
618                                 mDiscardEvent = true;
619                                 resetKeyPress(0);
620                             }
621                         }
622                     } else {
623                         responseKeyEvent(mSoftKeyDown);
624                         long timeout;
625                         if (mResponseTimes < LONG_PRESS_KEYNUM1) {
626                             timeout = LONG_PRESS_TIMEOUT1;
627                         } else if (mResponseTimes < LONG_PRESS_KEYNUM2) {
628                             timeout = LONG_PRESS_TIMEOUT2;
629                         } else {
630                             timeout = LONG_PRESS_TIMEOUT3;
631                         }
632                         postAtTime(this, SystemClock.uptimeMillis() + timeout);
633                     }
634                 } else {
635                     if (1 == mResponseTimes) {
636                         popupSymbols();
637                     }
638                 }
639             }
640         }
641     }
642 }
643