• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.keyboard;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.content.res.Resources;
22 import android.graphics.Canvas;
23 import android.os.Message;
24 import android.os.SystemClock;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.GestureDetector;
28 import android.view.LayoutInflater;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.ViewConfiguration;
32 import android.view.ViewGroup;
33 import android.view.accessibility.AccessibilityEvent;
34 import android.widget.PopupWindow;
35 
36 import com.android.inputmethod.accessibility.AccessibilityUtils;
37 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
38 import com.android.inputmethod.deprecated.VoiceProxy;
39 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
40 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
41 import com.android.inputmethod.latin.LatinIME;
42 import com.android.inputmethod.latin.R;
43 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
44 import com.android.inputmethod.latin.Utils;
45 
46 import java.util.WeakHashMap;
47 
48 /**
49  * A view that is responsible for detecting key presses and touch movements.
50  *
51  * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
52  * @attr ref R.styleable#KeyboardView_verticalCorrection
53  * @attr ref R.styleable#KeyboardView_popupLayout
54  */
55 public class LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
56         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
57     private static final String TAG = LatinKeyboardView.class.getSimpleName();
58 
59     private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
60 
61     private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
62 
63     // Timing constants
64     private final int mKeyRepeatInterval;
65 
66     // Mini keyboard
67     private PopupWindow mMoreKeysWindow;
68     private MoreKeysPanel mMoreKeysPanel;
69     private int mMoreKeysPanelPointerTrackerId;
70     private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
71             new WeakHashMap<Key, MoreKeysPanel>();
72 
73     /** Listener for {@link KeyboardActionListener}. */
74     private KeyboardActionListener mKeyboardActionListener;
75 
76     private final boolean mHasDistinctMultitouch;
77     private int mOldPointerCount = 1;
78     private int mOldKeyIndex;
79 
80     private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
81     protected KeyDetector mKeyDetector;
82 
83     // To detect double tap.
84     protected GestureDetector mGestureDetector;
85 
86     private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this);
87 
88     private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView>
89             implements TimerProxy {
90         private static final int MSG_REPEAT_KEY = 1;
91         private static final int MSG_LONGPRESS_KEY = 2;
92         private static final int MSG_IGNORE_DOUBLE_TAP = 3;
93 
94         private boolean mInKeyRepeat;
95 
KeyTimerHandler(LatinKeyboardView outerInstance)96         public KeyTimerHandler(LatinKeyboardView outerInstance) {
97             super(outerInstance);
98         }
99 
100         @Override
handleMessage(Message msg)101         public void handleMessage(Message msg) {
102             final LatinKeyboardView keyboardView = getOuterInstance();
103             final PointerTracker tracker = (PointerTracker) msg.obj;
104             switch (msg.what) {
105             case MSG_REPEAT_KEY:
106                 tracker.onRepeatKey(msg.arg1);
107                 startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker);
108                 break;
109             case MSG_LONGPRESS_KEY:
110                 keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
111                 break;
112             }
113         }
114 
115         @Override
startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker)116         public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
117             mInKeyRepeat = true;
118             sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
119         }
120 
cancelKeyRepeatTimer()121         public void cancelKeyRepeatTimer() {
122             mInKeyRepeat = false;
123             removeMessages(MSG_REPEAT_KEY);
124         }
125 
isInKeyRepeat()126         public boolean isInKeyRepeat() {
127             return mInKeyRepeat;
128         }
129 
130         @Override
startLongPressTimer(long delay, int keyIndex, PointerTracker tracker)131         public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
132             cancelLongPressTimer();
133             sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
134         }
135 
136         @Override
cancelLongPressTimer()137         public void cancelLongPressTimer() {
138             removeMessages(MSG_LONGPRESS_KEY);
139         }
140 
141         @Override
cancelKeyTimers()142         public void cancelKeyTimers() {
143             cancelKeyRepeatTimer();
144             cancelLongPressTimer();
145             removeMessages(MSG_IGNORE_DOUBLE_TAP);
146         }
147 
startIgnoringDoubleTap()148         public void startIgnoringDoubleTap() {
149             sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
150                     ViewConfiguration.getDoubleTapTimeout());
151         }
152 
isIgnoringDoubleTap()153         public boolean isIgnoringDoubleTap() {
154             return hasMessages(MSG_IGNORE_DOUBLE_TAP);
155         }
156 
cancelAllMessages()157         public void cancelAllMessages() {
158             cancelKeyTimers();
159         }
160     }
161 
162     private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
163         private boolean mProcessingShiftDoubleTapEvent = false;
164 
165         @Override
onDoubleTap(MotionEvent firstDown)166         public boolean onDoubleTap(MotionEvent firstDown) {
167             final Keyboard keyboard = getKeyboard();
168             if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard
169                     && ((LatinKeyboard) keyboard).isAlphaKeyboard()) {
170                 final int pointerIndex = firstDown.getActionIndex();
171                 final int id = firstDown.getPointerId(pointerIndex);
172                 final PointerTracker tracker = getPointerTracker(id);
173                 // If the first down event is on shift key.
174                 if (tracker.isOnShiftKey((int) firstDown.getX(), (int) firstDown.getY())) {
175                     mProcessingShiftDoubleTapEvent = true;
176                     return true;
177                 }
178             }
179             mProcessingShiftDoubleTapEvent = false;
180             return false;
181         }
182 
183         @Override
onDoubleTapEvent(MotionEvent secondTap)184         public boolean onDoubleTapEvent(MotionEvent secondTap) {
185             if (mProcessingShiftDoubleTapEvent
186                     && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
187                 final MotionEvent secondDown = secondTap;
188                 final int pointerIndex = secondDown.getActionIndex();
189                 final int id = secondDown.getPointerId(pointerIndex);
190                 final PointerTracker tracker = getPointerTracker(id);
191                 // If the second down event is also on shift key.
192                 if (tracker.isOnShiftKey((int) secondDown.getX(), (int) secondDown.getY())) {
193                     // Detected a double tap on shift key. If we are in the ignoring double tap
194                     // mode, it means we have already turned off caps lock in
195                     // {@link KeyboardSwitcher#onReleaseShift} .
196                     onDoubleTapShiftKey(tracker, mKeyTimerHandler.isIgnoringDoubleTap());
197                     return true;
198                 }
199                 // Otherwise these events should not be handled as double tap.
200                 mProcessingShiftDoubleTapEvent = false;
201             }
202             return mProcessingShiftDoubleTapEvent;
203         }
204     }
205 
LatinKeyboardView(Context context, AttributeSet attrs)206     public LatinKeyboardView(Context context, AttributeSet attrs) {
207         this(context, attrs, R.attr.keyboardViewStyle);
208     }
209 
LatinKeyboardView(Context context, AttributeSet attrs, int defStyle)210     public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
211         super(context, attrs, defStyle);
212 
213         mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
214 
215         final Resources res = getResources();
216         mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
217                 R.bool.config_show_mini_keyboard_at_touched_point);
218         final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
219         mKeyDetector = new KeyDetector(keyHysteresisDistance);
220 
221         final boolean ignoreMultitouch = true;
222         mGestureDetector = new GestureDetector(
223                 getContext(), new DoubleTapListener(), null, ignoreMultitouch);
224         mGestureDetector.setIsLongpressEnabled(false);
225 
226         mHasDistinctMultitouch = context.getPackageManager()
227                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
228         mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
229 
230         PointerTracker.init(mHasDistinctMultitouch, getContext());
231     }
232 
startIgnoringDoubleTap()233     public void startIgnoringDoubleTap() {
234         if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
235             mKeyTimerHandler.startIgnoringDoubleTap();
236     }
237 
setKeyboardActionListener(KeyboardActionListener listener)238     public void setKeyboardActionListener(KeyboardActionListener listener) {
239         mKeyboardActionListener = listener;
240         PointerTracker.setKeyboardActionListener(listener);
241     }
242 
243     /**
244      * Returns the {@link KeyboardActionListener} object.
245      * @return the listener attached to this keyboard
246      */
247     @Override
getKeyboardActionListener()248     public KeyboardActionListener getKeyboardActionListener() {
249         return mKeyboardActionListener;
250     }
251 
252     @Override
getKeyDetector()253     public KeyDetector getKeyDetector() {
254         return mKeyDetector;
255     }
256 
257     @Override
getDrawingProxy()258     public DrawingProxy getDrawingProxy() {
259         return this;
260     }
261 
262     @Override
getTimerProxy()263     public TimerProxy getTimerProxy() {
264         return mKeyTimerHandler;
265     }
266 
267     @Override
setKeyPreviewPopupEnabled(boolean previewEnabled, int delay)268     public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
269         final Keyboard keyboard = getKeyboard();
270         if (keyboard instanceof LatinKeyboard) {
271             final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
272             if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
273                 // Phone and number keyboard never shows popup preview.
274                 super.setKeyPreviewPopupEnabled(false, delay);
275                 return;
276             }
277         }
278         super.setKeyPreviewPopupEnabled(previewEnabled, delay);
279     }
280 
281     /**
282      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
283      * view will re-layout itself to accommodate the keyboard.
284      * @see Keyboard
285      * @see #getKeyboard()
286      * @param keyboard the keyboard to display in this view
287      */
288     @Override
setKeyboard(Keyboard keyboard)289     public void setKeyboard(Keyboard keyboard) {
290         // Remove any pending messages, except dismissing preview
291         mKeyTimerHandler.cancelKeyTimers();
292         super.setKeyboard(keyboard);
293         mKeyDetector.setKeyboard(
294                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
295         mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
296         PointerTracker.setKeyDetector(mKeyDetector);
297         mTouchScreenRegulator.setKeyboard(keyboard);
298         mMoreKeysPanelCache.clear();
299     }
300 
301     /**
302      * Returns whether the device has distinct multi-touch panel.
303      * @return true if the device has distinct multi-touch panel.
304      */
hasDistinctMultitouch()305     public boolean hasDistinctMultitouch() {
306         return mHasDistinctMultitouch;
307     }
308 
309     /**
310      * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
311      * codes for adjacent keys.  When disabled, only the primary key code will be
312      * reported.
313      * @param enabled whether or not the proximity correction is enabled
314      */
setProximityCorrectionEnabled(boolean enabled)315     public void setProximityCorrectionEnabled(boolean enabled) {
316         mKeyDetector.setProximityCorrectionEnabled(enabled);
317     }
318 
319     /**
320      * Returns true if proximity correction is enabled.
321      */
isProximityCorrectionEnabled()322     public boolean isProximityCorrectionEnabled() {
323         return mKeyDetector.isProximityCorrectionEnabled();
324     }
325 
326     @Override
cancelAllMessages()327     public void cancelAllMessages() {
328         mKeyTimerHandler.cancelAllMessages();
329         super.cancelAllMessages();
330     }
331 
openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker)332     private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
333         // Check if we have a popup layout specified first.
334         if (mMoreKeysLayout == 0) {
335             return false;
336         }
337 
338         // Check if we are already displaying popup panel.
339         if (mMoreKeysPanel != null)
340             return false;
341         final Key parentKey = tracker.getKey(keyIndex);
342         if (parentKey == null)
343             return false;
344         return onLongPress(parentKey, tracker);
345     }
346 
onDoubleTapShiftKey(@uppressWarnings"unused") PointerTracker tracker, final boolean ignore)347     private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker,
348             final boolean ignore) {
349         // When shift key is double tapped, the first tap is correctly processed as usual tap. And
350         // the second tap is treated as this double tap event, so that we need not mark tracker
351         // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
352         final int primaryCode = ignore ? Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY
353                 : Keyboard.CODE_CAPSLOCK;
354         mKeyboardActionListener.onCodeInput(primaryCode, null, 0, 0);
355     }
356 
357     // This default implementation returns a more keys panel.
onCreateMoreKeysPanel(Key parentKey)358     protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) {
359         if (parentKey.mMoreKeys == null)
360             return null;
361 
362         final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null);
363         if (container == null)
364             throw new NullPointerException();
365 
366         final MiniKeyboardView miniKeyboardView =
367                 (MiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
368         final Keyboard parentKeyboard = getKeyboard();
369         final Keyboard miniKeyboard = new MiniKeyboard.Builder(
370                 this, parentKeyboard.mMoreKeysTemplate, parentKey, parentKeyboard).build();
371         miniKeyboardView.setKeyboard(miniKeyboard);
372         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
373 
374         return miniKeyboardView;
375     }
376 
setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard)377     public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
378         final Keyboard keyboard = getKeyboard();
379         // We should not set text fade factor to the keyboard which does not display the language on
380         // its spacebar.
381         if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
382             ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
383         }
384     }
385 
386     /**
387      * Called when a key is long pressed. By default this will open mini keyboard associated
388      * with this key.
389      * @param parentKey the key that was long pressed
390      * @param tracker the pointer tracker which pressed the parent key
391      * @return true if the long press is handled, false otherwise. Subclasses should call the
392      * method on the base class if the subclass doesn't wish to handle the call.
393      */
onLongPress(Key parentKey, PointerTracker tracker)394     protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
395         final int primaryCode = parentKey.mCode;
396         final Keyboard keyboard = getKeyboard();
397         if (keyboard instanceof LatinKeyboard) {
398             final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
399             if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
400                 tracker.onLongPressed();
401                 // Long pressing on 0 in phone number keypad gives you a '+'.
402                 return invokeOnKey(Keyboard.CODE_PLUS);
403             }
404             if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) {
405                 tracker.onLongPressed();
406                 return invokeOnKey(Keyboard.CODE_CAPSLOCK);
407             }
408         }
409         if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
410             // Both long pressing settings key and space key invoke IME switcher dialog.
411             if (getKeyboardActionListener().onCustomRequest(
412                     LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
413                 tracker.onLongPressed();
414                 return true;
415             } else {
416                 return openMoreKeysPanel(parentKey, tracker);
417             }
418         } else {
419             return openMoreKeysPanel(parentKey, tracker);
420         }
421     }
422 
invokeOnKey(int primaryCode)423     private boolean invokeOnKey(int primaryCode) {
424         getKeyboardActionListener().onCodeInput(primaryCode, null,
425                 KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
426                 KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
427         return true;
428     }
429 
openMoreKeysPanel(Key parentKey, PointerTracker tracker)430     private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) {
431         MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey);
432         if (moreKeysPanel == null) {
433             moreKeysPanel = onCreateMoreKeysPanel(parentKey);
434             if (moreKeysPanel == null)
435                 return false;
436             mMoreKeysPanelCache.put(parentKey, moreKeysPanel);
437         }
438         if (mMoreKeysWindow == null) {
439             mMoreKeysWindow = new PopupWindow(getContext());
440             mMoreKeysWindow.setBackgroundDrawable(null);
441             mMoreKeysWindow.setAnimationStyle(R.style.MiniKeyboardAnimation);
442         }
443         mMoreKeysPanel = moreKeysPanel;
444         mMoreKeysPanelPointerTrackerId = tracker.mPointerId;
445 
446         final Keyboard keyboard = getKeyboard();
447         moreKeysPanel.setShifted(keyboard.isShiftedOrShiftLocked());
448         final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
449                 : parentKey.mX + parentKey.mWidth / 2;
450         final int pointY = parentKey.mY - keyboard.mVerticalGap;
451         moreKeysPanel.showMoreKeysPanel(
452                 this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener());
453         final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
454         final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
455         tracker.onShowMoreKeysPanel(
456                 translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
457         dimEntireKeyboard(true);
458         return true;
459     }
460 
getPointerTracker(final int id)461     private PointerTracker getPointerTracker(final int id) {
462         return PointerTracker.getPointerTracker(id, this);
463     }
464 
isInSlidingKeyInput()465     public boolean isInSlidingKeyInput() {
466         if (mMoreKeysPanel != null) {
467             return true;
468         } else {
469             return PointerTracker.isAnyInSlidingKeyInput();
470         }
471     }
472 
getPointerCount()473     public int getPointerCount() {
474         return mOldPointerCount;
475     }
476 
477     @Override
onTouchEvent(MotionEvent me)478     public boolean onTouchEvent(MotionEvent me) {
479         if (getKeyboard() == null) {
480             return false;
481         }
482         return mTouchScreenRegulator.onTouchEvent(me);
483     }
484 
485     @Override
processMotionEvent(MotionEvent me)486     public boolean processMotionEvent(MotionEvent me) {
487         final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
488         final int action = me.getActionMasked();
489         final int pointerCount = me.getPointerCount();
490         final int oldPointerCount = mOldPointerCount;
491         mOldPointerCount = pointerCount;
492 
493         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
494         // If the device does not have distinct multi-touch support panel, ignore all multi-touch
495         // events except a transition from/to single-touch.
496         if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
497             return true;
498         }
499 
500         // Gesture detector must be enabled only when mini-keyboard is not on the screen.
501         if (mMoreKeysPanel == null && mGestureDetector != null
502                 && mGestureDetector.onTouchEvent(me)) {
503             PointerTracker.dismissAllKeyPreviews();
504             mKeyTimerHandler.cancelKeyTimers();
505             return true;
506         }
507 
508         final long eventTime = me.getEventTime();
509         final int index = me.getActionIndex();
510         final int id = me.getPointerId(index);
511         final int x, y;
512         if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) {
513             x = mMoreKeysPanel.translateX((int)me.getX(index));
514             y = mMoreKeysPanel.translateY((int)me.getY(index));
515         } else {
516             x = (int)me.getX(index);
517             y = (int)me.getY(index);
518         }
519 
520         if (mKeyTimerHandler.isInKeyRepeat()) {
521             final PointerTracker tracker = getPointerTracker(id);
522             // Key repeating timer will be canceled if 2 or more keys are in action, and current
523             // event (UP or DOWN) is non-modifier key.
524             if (pointerCount > 1 && !tracker.isModifier()) {
525                 mKeyTimerHandler.cancelKeyRepeatTimer();
526             }
527             // Up event will pass through.
528         }
529 
530         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
531         // Translate mutli-touch event to single-touch events on the device that has no distinct
532         // multi-touch panel.
533         if (nonDistinctMultitouch) {
534             // Use only main (id=0) pointer tracker.
535             PointerTracker tracker = getPointerTracker(0);
536             if (pointerCount == 1 && oldPointerCount == 2) {
537                 // Multi-touch to single touch transition.
538                 // Send a down event for the latest pointer if the key is different from the
539                 // previous key.
540                 final int newKeyIndex = tracker.getKeyIndexOn(x, y);
541                 if (mOldKeyIndex != newKeyIndex) {
542                     tracker.onDownEvent(x, y, eventTime, this);
543                     if (action == MotionEvent.ACTION_UP)
544                         tracker.onUpEvent(x, y, eventTime);
545                 }
546             } else if (pointerCount == 2 && oldPointerCount == 1) {
547                 // Single-touch to multi-touch transition.
548                 // Send an up event for the last pointer.
549                 final int lastX = tracker.getLastX();
550                 final int lastY = tracker.getLastY();
551                 mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
552                 tracker.onUpEvent(lastX, lastY, eventTime);
553             } else if (pointerCount == 1 && oldPointerCount == 1) {
554                 tracker.processMotionEvent(action, x, y, eventTime, this);
555             } else {
556                 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
557                         + " (old " + oldPointerCount + ")");
558             }
559             return true;
560         }
561 
562         if (action == MotionEvent.ACTION_MOVE) {
563             for (int i = 0; i < pointerCount; i++) {
564                 final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
565                 final int px, py;
566                 if (mMoreKeysPanel != null
567                         && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
568                     px = mMoreKeysPanel.translateX((int)me.getX(i));
569                     py = mMoreKeysPanel.translateY((int)me.getY(i));
570                 } else {
571                     px = (int)me.getX(i);
572                     py = (int)me.getY(i);
573                 }
574                 tracker.onMoveEvent(px, py, eventTime);
575             }
576         } else {
577             getPointerTracker(id).processMotionEvent(action, x, y, eventTime, this);
578         }
579 
580         return true;
581     }
582 
583     @Override
closing()584     public void closing() {
585         super.closing();
586         dismissMoreKeysPanel();
587         mMoreKeysPanelCache.clear();
588     }
589 
590     @Override
dismissMoreKeysPanel()591     public boolean dismissMoreKeysPanel() {
592         if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) {
593             mMoreKeysWindow.dismiss();
594             mMoreKeysPanel = null;
595             mMoreKeysPanelPointerTrackerId = -1;
596             dimEntireKeyboard(false);
597             return true;
598         }
599         return false;
600     }
601 
handleBack()602     public boolean handleBack() {
603         return dismissMoreKeysPanel();
604     }
605 
606     @Override
draw(Canvas c)607     public void draw(Canvas c) {
608         Utils.GCUtils.getInstance().reset();
609         boolean tryGC = true;
610         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
611             try {
612                 super.draw(c);
613                 tryGC = false;
614             } catch (OutOfMemoryError e) {
615                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
616             }
617         }
618     }
619 
620     @Override
onAttachedToWindow()621     protected void onAttachedToWindow() {
622         // Token is available from here.
623         VoiceProxy.getInstance().onAttachedToWindow();
624     }
625 
626     @Override
dispatchTouchEvent(MotionEvent event)627     public boolean dispatchTouchEvent(MotionEvent event) {
628         // Drop non-hover touch events when touch exploration is enabled.
629         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
630             return false;
631         }
632 
633         return super.dispatchTouchEvent(event);
634     }
635 
636     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)637     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
638         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
639             final PointerTracker tracker = getPointerTracker(0);
640             return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
641                     event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
642         }
643 
644         return super.dispatchPopulateAccessibilityEvent(event);
645     }
646 
647     /**
648      * Receives hover events from the input framework. This method overrides
649      * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On
650      * lower SDK versions, this method is never called.
651      *
652      * @param event The motion event to be dispatched.
653      * @return {@code true} if the event was handled by the view, {@code false}
654      *         otherwise
655      */
dispatchHoverEvent(MotionEvent event)656     public boolean dispatchHoverEvent(MotionEvent event) {
657         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
658             final PointerTracker tracker = getPointerTracker(0);
659             return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
660         }
661 
662         // Reflection doesn't support calling superclass methods.
663         return false;
664     }
665 }
666