Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      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 android.content.Context;
     20 import android.graphics.Canvas;
     21 import android.graphics.Paint;
     22 import android.inputmethodservice.Keyboard;
     23 import android.inputmethodservice.Keyboard.Key;
     24 import android.os.Handler;
     25 import android.os.Message;
     26 import android.os.SystemClock;
     27 import android.text.TextUtils;
     28 import android.util.AttributeSet;
     29 import android.view.MotionEvent;
     30 
     31 import java.util.List;
     32 
     33 public class LatinKeyboardView extends LatinKeyboardBaseView {
     34 
     35     static final int KEYCODE_OPTIONS = -100;
     36     static final int KEYCODE_OPTIONS_LONGPRESS = -101;
     37     static final int KEYCODE_VOICE = -102;
     38     static final int KEYCODE_F1 = -103;
     39     static final int KEYCODE_NEXT_LANGUAGE = -104;
     40     static final int KEYCODE_PREV_LANGUAGE = -105;
     41 
     42     private Keyboard mPhoneKeyboard;
     43 
     44     /** Whether we've started dropping move events because we found a big jump */
     45     private boolean mDroppingEvents;
     46     /**
     47      * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
     48      * occured
     49      */
     50     private boolean mDisableDisambiguation;
     51     /** The distance threshold at which we start treating the touch session as a multi-touch */
     52     private int mJumpThresholdSquare = Integer.MAX_VALUE;
     53     /** The y coordinate of the last row */
     54     private int mLastRowY;
     55 
     56     public LatinKeyboardView(Context context, AttributeSet attrs) {
     57         this(context, attrs, 0);
     58     }
     59 
     60     public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
     61         super(context, attrs, defStyle);
     62     }
     63 
     64     public void setPhoneKeyboard(Keyboard phoneKeyboard) {
     65         mPhoneKeyboard = phoneKeyboard;
     66     }
     67 
     68     @Override
     69     public void setPreviewEnabled(boolean previewEnabled) {
     70         if (getKeyboard() == mPhoneKeyboard) {
     71             // Phone keyboard never shows popup preview (except language switch).
     72             super.setPreviewEnabled(false);
     73         } else {
     74             super.setPreviewEnabled(previewEnabled);
     75         }
     76     }
     77 
     78     @Override
     79     public void setKeyboard(Keyboard newKeyboard) {
     80         final Keyboard oldKeyboard = getKeyboard();
     81         if (oldKeyboard instanceof LatinKeyboard) {
     82             // Reset old keyboard state before switching to new keyboard.
     83             ((LatinKeyboard)oldKeyboard).keyReleased();
     84         }
     85         super.setKeyboard(newKeyboard);
     86         // One-seventh of the keyboard width seems like a reasonable threshold
     87         mJumpThresholdSquare = newKeyboard.getMinWidth() / 7;
     88         mJumpThresholdSquare *= mJumpThresholdSquare;
     89         // Assuming there are 4 rows, this is the coordinate of the last row
     90         mLastRowY = (newKeyboard.getHeight() * 3) / 4;
     91         setKeyboardLocal(newKeyboard);
     92     }
     93 
     94     @Override
     95     protected boolean onLongPress(Key key) {
     96         int primaryCode = key.codes[0];
     97         if (primaryCode == KEYCODE_OPTIONS) {
     98             return invokeOnKey(KEYCODE_OPTIONS_LONGPRESS);
     99         } else if (primaryCode == '0' && getKeyboard() == mPhoneKeyboard) {
    100             // Long pressing on 0 in phone number keypad gives you a '+'.
    101             return invokeOnKey('+');
    102         } else {
    103             return super.onLongPress(key);
    104         }
    105     }
    106 
    107     private boolean invokeOnKey(int primaryCode) {
    108         getOnKeyboardActionListener().onKey(primaryCode, null,
    109                 LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
    110                 LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
    111         return true;
    112     }
    113 
    114     @Override
    115     protected CharSequence adjustCase(CharSequence label) {
    116         Keyboard keyboard = getKeyboard();
    117         if (keyboard.isShifted()
    118                 && keyboard instanceof LatinKeyboard
    119                 && ((LatinKeyboard) keyboard).isAlphaKeyboard()
    120                 && !TextUtils.isEmpty(label) && label.length() < 3
    121                 && Character.isLowerCase(label.charAt(0))) {
    122             return label.toString().toUpperCase(getKeyboardLocale());
    123         }
    124         return label;
    125     }
    126 
    127     public boolean setShiftLocked(boolean shiftLocked) {
    128         Keyboard keyboard = getKeyboard();
    129         if (keyboard instanceof LatinKeyboard) {
    130             ((LatinKeyboard)keyboard).setShiftLocked(shiftLocked);
    131             invalidateAllKeys();
    132             return true;
    133         }
    134         return false;
    135     }
    136 
    137     /**
    138      * This function checks to see if we need to handle any sudden jumps in the pointer location
    139      * that could be due to a multi-touch being treated as a move by the firmware or hardware.
    140      * Once a sudden jump is detected, all subsequent move events are discarded
    141      * until an UP is received.<P>
    142      * When a sudden jump is detected, an UP event is simulated at the last position and when
    143      * the sudden moves subside, a DOWN event is simulated for the second key.
    144      * @param me the motion event
    145      * @return true if the event was consumed, so that it doesn't continue to be handled by
    146      * KeyboardView.
    147      */
    148     private boolean handleSuddenJump(MotionEvent me) {
    149         final int action = me.getAction();
    150         final int x = (int) me.getX();
    151         final int y = (int) me.getY();
    152         boolean result = false;
    153 
    154         // Real multi-touch event? Stop looking for sudden jumps
    155         if (me.getPointerCount() > 1) {
    156             mDisableDisambiguation = true;
    157         }
    158         if (mDisableDisambiguation) {
    159             // If UP, reset the multi-touch flag
    160             if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
    161             return false;
    162         }
    163 
    164         switch (action) {
    165         case MotionEvent.ACTION_DOWN:
    166             // Reset the "session"
    167             mDroppingEvents = false;
    168             mDisableDisambiguation = false;
    169             break;
    170         case MotionEvent.ACTION_MOVE:
    171             // Is this a big jump?
    172             final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
    173             // Check the distance and also if the move is not entirely within the bottom row
    174             // If it's only in the bottom row, it might be an intentional slide gesture
    175             // for language switching
    176             if (distanceSquare > mJumpThresholdSquare
    177                     && (mLastY < mLastRowY || y < mLastRowY)) {
    178                 // If we're not yet dropping events, start dropping and send an UP event
    179                 if (!mDroppingEvents) {
    180                     mDroppingEvents = true;
    181                     // Send an up event
    182                     MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
    183                             MotionEvent.ACTION_UP,
    184                             mLastX, mLastY, me.getMetaState());
    185                     super.onTouchEvent(translated);
    186                     translated.recycle();
    187                 }
    188                 result = true;
    189             } else if (mDroppingEvents) {
    190                 // If moves are small and we're already dropping events, continue dropping
    191                 result = true;
    192             }
    193             break;
    194         case MotionEvent.ACTION_UP:
    195             if (mDroppingEvents) {
    196                 // Send a down event first, as we dropped a bunch of sudden jumps and assume that
    197                 // the user is releasing the touch on the second key.
    198                 MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
    199                         MotionEvent.ACTION_DOWN,
    200                         x, y, me.getMetaState());
    201                 super.onTouchEvent(translated);
    202                 translated.recycle();
    203                 mDroppingEvents = false;
    204                 // Let the up event get processed as well, result = false
    205             }
    206             break;
    207         }
    208         // Track the previous coordinate
    209         mLastX = x;
    210         mLastY = y;
    211         return result;
    212     }
    213 
    214     @Override
    215     public boolean onTouchEvent(MotionEvent me) {
    216         LatinKeyboard keyboard = (LatinKeyboard) getKeyboard();
    217         if (DEBUG_LINE) {
    218             mLastX = (int) me.getX();
    219             mLastY = (int) me.getY();
    220             invalidate();
    221         }
    222 
    223         // If there was a sudden jump, return without processing the actual motion event.
    224         if (handleSuddenJump(me))
    225             return true;
    226 
    227         // Reset any bounding box controls in the keyboard
    228         if (me.getAction() == MotionEvent.ACTION_DOWN) {
    229             keyboard.keyReleased();
    230         }
    231 
    232         if (me.getAction() == MotionEvent.ACTION_UP) {
    233             int languageDirection = keyboard.getLanguageChangeDirection();
    234             if (languageDirection != 0) {
    235                 getOnKeyboardActionListener().onKey(
    236                         languageDirection == 1 ? KEYCODE_NEXT_LANGUAGE : KEYCODE_PREV_LANGUAGE,
    237                         null, mLastX, mLastY);
    238                 me.setAction(MotionEvent.ACTION_CANCEL);
    239                 keyboard.keyReleased();
    240                 return super.onTouchEvent(me);
    241             }
    242         }
    243 
    244         return super.onTouchEvent(me);
    245     }
    246 
    247     /****************************  INSTRUMENTATION  *******************************/
    248 
    249     static final boolean DEBUG_AUTO_PLAY = false;
    250     static final boolean DEBUG_LINE = false;
    251     private static final int MSG_TOUCH_DOWN = 1;
    252     private static final int MSG_TOUCH_UP = 2;
    253 
    254     Handler mHandler2;
    255 
    256     private String mStringToPlay;
    257     private int mStringIndex;
    258     private boolean mDownDelivered;
    259     private Key[] mAsciiKeys = new Key[256];
    260     private boolean mPlaying;
    261     private int mLastX;
    262     private int mLastY;
    263     private Paint mPaint;
    264 
    265     private void setKeyboardLocal(Keyboard k) {
    266         if (DEBUG_AUTO_PLAY) {
    267             findKeys();
    268             if (mHandler2 == null) {
    269                 mHandler2 = new Handler() {
    270                     @Override
    271                     public void handleMessage(Message msg) {
    272                         removeMessages(MSG_TOUCH_DOWN);
    273                         removeMessages(MSG_TOUCH_UP);
    274                         if (mPlaying == false) return;
    275 
    276                         switch (msg.what) {
    277                             case MSG_TOUCH_DOWN:
    278                                 if (mStringIndex >= mStringToPlay.length()) {
    279                                     mPlaying = false;
    280                                     return;
    281                                 }
    282                                 char c = mStringToPlay.charAt(mStringIndex);
    283                                 while (c > 255 || mAsciiKeys[c] == null) {
    284                                     mStringIndex++;
    285                                     if (mStringIndex >= mStringToPlay.length()) {
    286                                         mPlaying = false;
    287                                         return;
    288                                     }
    289                                     c = mStringToPlay.charAt(mStringIndex);
    290                                 }
    291                                 int x = mAsciiKeys[c].x + 10;
    292                                 int y = mAsciiKeys[c].y + 26;
    293                                 MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(),
    294                                         SystemClock.uptimeMillis(),
    295                                         MotionEvent.ACTION_DOWN, x, y, 0);
    296                                 LatinKeyboardView.this.dispatchTouchEvent(me);
    297                                 me.recycle();
    298                                 sendEmptyMessageDelayed(MSG_TOUCH_UP, 500); // Deliver up in 500ms if nothing else
    299                                 // happens
    300                                 mDownDelivered = true;
    301                                 break;
    302                             case MSG_TOUCH_UP:
    303                                 char cUp = mStringToPlay.charAt(mStringIndex);
    304                                 int x2 = mAsciiKeys[cUp].x + 10;
    305                                 int y2 = mAsciiKeys[cUp].y + 26;
    306                                 mStringIndex++;
    307 
    308                                 MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(),
    309                                         SystemClock.uptimeMillis(),
    310                                         MotionEvent.ACTION_UP, x2, y2, 0);
    311                                 LatinKeyboardView.this.dispatchTouchEvent(me2);
    312                                 me2.recycle();
    313                                 sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 500); // Deliver up in 500ms if nothing else
    314                                 // happens
    315                                 mDownDelivered = false;
    316                                 break;
    317                         }
    318                     }
    319                 };
    320 
    321             }
    322         }
    323     }
    324 
    325     private void findKeys() {
    326         List<Key> keys = getKeyboard().getKeys();
    327         // Get the keys on this keyboard
    328         for (int i = 0; i < keys.size(); i++) {
    329             int code = keys.get(i).codes[0];
    330             if (code >= 0 && code <= 255) {
    331                 mAsciiKeys[code] = keys.get(i);
    332             }
    333         }
    334     }
    335 
    336     public void startPlaying(String s) {
    337         if (DEBUG_AUTO_PLAY) {
    338             if (s == null) return;
    339             mStringToPlay = s.toLowerCase();
    340             mPlaying = true;
    341             mDownDelivered = false;
    342             mStringIndex = 0;
    343             mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10);
    344         }
    345     }
    346 
    347     @Override
    348     public void draw(Canvas c) {
    349         LatinIMEUtil.GCUtils.getInstance().reset();
    350         boolean tryGC = true;
    351         for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
    352             try {
    353                 super.draw(c);
    354                 tryGC = false;
    355             } catch (OutOfMemoryError e) {
    356                 tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
    357             }
    358         }
    359         if (DEBUG_AUTO_PLAY) {
    360             if (mPlaying) {
    361                 mHandler2.removeMessages(MSG_TOUCH_DOWN);
    362                 mHandler2.removeMessages(MSG_TOUCH_UP);
    363                 if (mDownDelivered) {
    364                     mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20);
    365                 } else {
    366                     mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20);
    367                 }
    368             }
    369         }
    370         if (DEBUG_LINE) {
    371             if (mPaint == null) {
    372                 mPaint = new Paint();
    373                 mPaint.setColor(0x80FFFFFF);
    374                 mPaint.setAntiAlias(false);
    375             }
    376             c.drawLine(mLastX, 0, mLastX, getHeight(), mPaint);
    377             c.drawLine(0, mLastY, getWidth(), mLastY, mPaint);
    378         }
    379     }
    380 }
    381