SuddenJumpingTouchEventHandler.java revision c403a46f6d787b79768895272d53d296100677dd
1/*
2 * Copyright (C) 2011 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
17package com.android.inputmethod.keyboard;
18
19import android.content.Context;
20import android.os.Build;
21import android.util.Log;
22import android.view.MotionEvent;
23
24import com.android.inputmethod.latin.LatinImeLogger;
25import com.android.inputmethod.latin.R;
26
27public class SuddenJumpingTouchEventHandler {
28    private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName();
29    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
30
31    public interface ProcessMotionEvent {
32        public boolean processMotionEvent(MotionEvent me);
33    }
34
35    private final ProcessMotionEvent mView;
36    private final boolean mNeedsSuddenJumpingHack;
37
38    /** Whether we've started dropping move events because we found a big jump */
39    private boolean mDroppingEvents;
40    /**
41     * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
42     * occured
43     */
44    private boolean mDisableDisambiguation;
45    /** The distance threshold at which we start treating the touch session as a multi-touch */
46    private int mJumpThresholdSquare = Integer.MAX_VALUE;
47    private int mLastX;
48    private int mLastY;
49
50    public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
51        mView = view;
52        final String[] deviceList = context.getResources().getStringArray(
53                R.array.sudden_jumping_touch_event_device_list);
54        mNeedsSuddenJumpingHack = needsSuddenJumpingHack(Build.DEVICE, deviceList);
55    }
56
57    private static boolean needsSuddenJumpingHack(String deviceName, String[] deviceList) {
58        for (String device : deviceList) {
59            if (device.equalsIgnoreCase(deviceName)) {
60                return true;
61            }
62        }
63        return false;
64    }
65
66    public void setKeyboard(Keyboard newKeyboard) {
67        // One-seventh of the keyboard width seems like a reasonable threshold
68        final int jumpThreshold = newKeyboard.mOccupiedWidth / 7;
69        mJumpThresholdSquare = jumpThreshold * jumpThreshold;
70    }
71
72    /**
73     * This function checks to see if we need to handle any sudden jumps in the pointer location
74     * that could be due to a multi-touch being treated as a move by the firmware or hardware.
75     * Once a sudden jump is detected, all subsequent move events are discarded
76     * until an UP is received.<P>
77     * When a sudden jump is detected, an UP event is simulated at the last position and when
78     * the sudden moves subside, a DOWN event is simulated for the second key.
79     * @param me the motion event
80     * @return true if the event was consumed, so that it doesn't continue to be handled by
81     * {@link LatinKeyboardBaseView}.
82     */
83    private boolean handleSuddenJumping(MotionEvent me) {
84        if (!mNeedsSuddenJumpingHack)
85            return false;
86        final int action = me.getAction();
87        final int x = (int) me.getX();
88        final int y = (int) me.getY();
89        boolean result = false;
90
91        // Real multi-touch event? Stop looking for sudden jumps
92        if (me.getPointerCount() > 1) {
93            mDisableDisambiguation = true;
94        }
95        if (mDisableDisambiguation) {
96            // If UP, reset the multi-touch flag
97            if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
98            return false;
99        }
100
101        switch (action) {
102        case MotionEvent.ACTION_DOWN:
103            // Reset the "session"
104            mDroppingEvents = false;
105            mDisableDisambiguation = false;
106            break;
107        case MotionEvent.ACTION_MOVE:
108            // Is this a big jump?
109            final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
110            // Check the distance.
111            if (distanceSquare > mJumpThresholdSquare) {
112                // If we're not yet dropping events, start dropping and send an UP event
113                if (!mDroppingEvents) {
114                    mDroppingEvents = true;
115                    // Send an up event
116                    MotionEvent translated = MotionEvent.obtain(
117                            me.getEventTime(), me.getEventTime(),
118                            MotionEvent.ACTION_UP,
119                            mLastX, mLastY, me.getMetaState());
120                    mView.processMotionEvent(translated);
121                    translated.recycle();
122                }
123                result = true;
124            } else if (mDroppingEvents) {
125                // If moves are small and we're already dropping events, continue dropping
126                result = true;
127            }
128            break;
129        case MotionEvent.ACTION_UP:
130            if (mDroppingEvents) {
131                // Send a down event first, as we dropped a bunch of sudden jumps and assume that
132                // the user is releasing the touch on the second key.
133                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
134                        MotionEvent.ACTION_DOWN,
135                        x, y, me.getMetaState());
136                mView.processMotionEvent(translated);
137                translated.recycle();
138                mDroppingEvents = false;
139                // Let the up event get processed as well, result = false
140            }
141            break;
142        }
143        // Track the previous coordinate
144        mLastX = x;
145        mLastY = y;
146        return result;
147    }
148
149    public boolean onTouchEvent(MotionEvent me) {
150        // If there was a sudden jump, return without processing the actual motion event.
151        if (handleSuddenJumping(me)) {
152            if (DEBUG_MODE)
153                Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
154            return true;
155        }
156        return mView.processMotionEvent(me);
157    }
158}
159