Home | History | Annotate | Download | only in pinyin
      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 
    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 
    204     public void setService(InputMethodService service) {
    205         mService = service;
    206     }
    207 
    208     public void setInputModeSwitcher(InputModeSwitcher inputModeSwitcher) {
    209         mInputModeSwitcher = inputModeSwitcher;
    210     }
    211 
    212     public void setGestureDetector(GestureDetector gestureDetector) {
    213         mGestureDetector = gestureDetector;
    214     }
    215 
    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 
    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 
    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 
    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 
    311     private void responseKeyEvent(SoftKey sKey) {
    312         if (null == sKey) return;
    313         ((PinyinIME) mService).responseSoftKeyEvent(sKey);
    314         return;
    315     }
    316 
    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 
    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 
    378     private void dimSoftKeyboard(boolean dimSkb) {
    379         mMajorView.dimSoftKeyboard(dimSkb);
    380     }
    381 
    382     private void dismissPopupSkb() {
    383         mPopupSkb.dismiss();
    384         mPopupSkbShow = false;
    385         dimSoftKeyboard(false);
    386         resetKeyPress(0);
    387     }
    388 
    389     private void resetKeyPress(long delay) {
    390         mLongPressTimer.removeTimer();
    391 
    392         if (null != mSkv) {
    393             mSkv.resetKeyPress(delay);
    394         }
    395     }
    396 
    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 
    408     public void dismissPopups() {
    409         handleBack(true);
    410         resetKeyPress(0);
    411     }
    412 
    413     @Override
    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
    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.
    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 
    596         public LongPressTimer(SkbContainer skbContainer) {
    597             mSkbContainer = skbContainer;
    598         }
    599 
    600         public void startTimer() {
    601             postAtTime(this, SystemClock.uptimeMillis() + LONG_PRESS_TIMEOUT1);
    602             mResponseTimes = 0;
    603         }
    604 
    605         public boolean removeTimer() {
    606             removeCallbacks(this);
    607             return true;
    608         }
    609 
    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