• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LatinKeyboardView(Context context, AttributeSet attrs)56     public LatinKeyboardView(Context context, AttributeSet attrs) {
57         this(context, attrs, 0);
58     }
59 
LatinKeyboardView(Context context, AttributeSet attrs, int defStyle)60     public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
61         super(context, attrs, defStyle);
62     }
63 
setPhoneKeyboard(Keyboard phoneKeyboard)64     public void setPhoneKeyboard(Keyboard phoneKeyboard) {
65         mPhoneKeyboard = phoneKeyboard;
66     }
67 
68     @Override
setPreviewEnabled(boolean previewEnabled)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
setKeyboard(Keyboard newKeyboard)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
onLongPress(Key key)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 
invokeOnKey(int primaryCode)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
adjustCase(CharSequence label)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 
setShiftLocked(boolean shiftLocked)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      */
handleSuddenJump(MotionEvent me)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
onTouchEvent(MotionEvent me)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 
setKeyboardLocal(Keyboard k)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 
findKeys()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 
startPlaying(String s)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
draw(Canvas c)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