1923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/*
2c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka * Copyright (C) 2011 The Android Open Source Project
35a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *
4923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * use this file except in compliance with the License. You may obtain a copy of
6923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License at
75a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *
8923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0
95a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *
10923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * License for the specific language governing permissions and limitations under
14923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License.
15923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
16923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
1799906b3fc2dcb447aafdd43dda0c4551513b293eTadashi G. Takaokapackage com.android.inputmethod.keyboard.internal;
18923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
19923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.content.Context;
20faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaokaimport android.util.Log;
21923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.view.MotionEvent;
22d2a431efa726771dee5c7b90004a0ed670d9a129Tadashi G. Takaoka
2399906b3fc2dcb447aafdd43dda0c4551513b293eTadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
2499906b3fc2dcb447aafdd43dda0c4551513b293eTadashi G. Takaokaimport com.android.inputmethod.keyboard.MainKeyboardView;
2513a741999480343ccebd81ff6349b572bde17b07Tadashi G. Takaokaimport com.android.inputmethod.latin.LatinImeLogger;
26c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaokaimport com.android.inputmethod.latin.R;
279879f65651a748e4c0a45715eb7d5663652f1127Tadashi G. Takaokaimport com.android.inputmethod.latin.ResourceUtils;
289bc29d78a6ce83f77869aa63748176241e29d43cKurt Partridgeimport com.android.inputmethod.latin.define.ProductionFlag;
296b966160ac8570271547bf63217efa5e228d4accKurt Partridgeimport com.android.inputmethod.research.ResearchLogger;
3013a741999480343ccebd81ff6349b572bde17b07Tadashi G. Takaoka
31a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class SuddenJumpingTouchEventHandler {
32c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName();
33faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
348eb2e34d5b2def57c9548f88e37e5c9e5a0bea59Amith Yamasani
35c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    public interface ProcessMotionEvent {
36c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        public boolean processMotionEvent(MotionEvent me);
37c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    }
38c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka
39c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    private final ProcessMotionEvent mView;
40c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    private final boolean mNeedsSuddenJumpingHack;
41c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka
420fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    /** Whether we've started dropping move events because we found a big jump */
430fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    private boolean mDroppingEvents;
44e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka    /**
45d2a431efa726771dee5c7b90004a0ed670d9a129Tadashi G. Takaoka     * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
46d2a431efa726771dee5c7b90004a0ed670d9a129Tadashi G. Takaoka     * occured
470fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     */
480fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    private boolean mDisableDisambiguation;
490fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    /** The distance threshold at which we start treating the touch session as a multi-touch */
500fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    private int mJumpThresholdSquare = Integer.MAX_VALUE;
51e26ef1bccddc942fdaeada3409c8e8ff18a35008Tadashi G. Takaoka    private int mLastX;
52e26ef1bccddc942fdaeada3409c8e8ff18a35008Tadashi G. Takaoka    private int mLastY;
533f7d75060afb2bb7e74879bcbbdcb9700ec5c2d6Amith Yamasani
54c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
55c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        mView = view;
569879f65651a748e4c0a45715eb7d5663652f1127Tadashi G. Takaoka        mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
57624f1bab39357eb716dfc7ec6b723da3f926f5a2Tadashi G. Takaoka                context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false"));
58923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
59230cd6f7f27300e2688b5e5a22a5361f446b80e7Amith Yamasani
60050c0462dc2ada5a5afecec5b6745693c5066b85Tadashi G. Takaoka    public void setKeyboard(Keyboard newKeyboard) {
610fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        // One-seventh of the keyboard width seems like a reasonable threshold
628da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        final int jumpThreshold = newKeyboard.mOccupiedWidth / 7;
63851c3267d4ab21f892b4164783bb4959c88b44ffTadashi G. Takaoka        mJumpThresholdSquare = jumpThreshold * jumpThreshold;
640ce98cbf98c6409ac18fa341f467703d78352a4cKen Wakasa    }
650ce98cbf98c6409ac18fa341f467703d78352a4cKen Wakasa
660fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    /**
670fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     * This function checks to see if we need to handle any sudden jumps in the pointer location
680fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     * that could be due to a multi-touch being treated as a move by the firmware or hardware.
690fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     * Once a sudden jump is detected, all subsequent move events are discarded
700fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     * until an UP is received.<P>
71e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka     * When a sudden jump is detected, an UP event is simulated at the last position and when
720fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     * the sudden moves subside, a DOWN event is simulated for the second key.
730fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     * @param me the motion event
74e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka     * @return true if the event was consumed, so that it doesn't continue to be handled by
75c8e45ddb032554f4e9d4411d8ef47d98db62d77bTadashi G. Takaoka     * {@link MainKeyboardView}.
760fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani     */
77c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    private boolean handleSuddenJumping(MotionEvent me) {
78c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        if (!mNeedsSuddenJumpingHack)
799a5b592b27158b6fb8b7a89157bb995b182899d8Tadashi G. Takaoka            return false;
800fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        final int action = me.getAction();
810fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        final int x = (int) me.getX();
820fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        final int y = (int) me.getY();
830fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        boolean result = false;
840fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani
850fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        // Real multi-touch event? Stop looking for sudden jumps
860fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        if (me.getPointerCount() > 1) {
870fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            mDisableDisambiguation = true;
880fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        }
890fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        if (mDisableDisambiguation) {
900fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            // If UP, reset the multi-touch flag
910fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
920fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            return false;
930fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        }
940fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani
950fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        switch (action) {
960fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        case MotionEvent.ACTION_DOWN:
970fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            // Reset the "session"
980fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            mDroppingEvents = false;
990fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            mDisableDisambiguation = false;
1000fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            break;
1010fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        case MotionEvent.ACTION_MOVE:
1020fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            // Is this a big jump?
1030fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
104ef5dfc480c7a3e3e34a20b7aacc731942e7a0578Tadashi G. Takaoka            // Check the distance.
105ef5dfc480c7a3e3e34a20b7aacc731942e7a0578Tadashi G. Takaoka            if (distanceSquare > mJumpThresholdSquare) {
1060fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                // If we're not yet dropping events, start dropping and send an UP event
1070fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                if (!mDroppingEvents) {
1080fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                    mDroppingEvents = true;
1090fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                    // Send an up event
110240297d0ee186b14e795016e9b1bd168c8d8acf8Jean Chalard                    MotionEvent translated = MotionEvent.obtain(
111240297d0ee186b14e795016e9b1bd168c8d8acf8Jean Chalard                            me.getEventTime(), me.getEventTime(),
1120fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                            MotionEvent.ACTION_UP,
1130fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                            mLastX, mLastY, me.getMetaState());
114c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka                    mView.processMotionEvent(translated);
1150fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                    translated.recycle();
1160fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                }
1170fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                result = true;
1180fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            } else if (mDroppingEvents) {
1190fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                // If moves are small and we're already dropping events, continue dropping
1200fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                result = true;
1210fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            }
1220fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            break;
1230fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        case MotionEvent.ACTION_UP:
1240fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            if (mDroppingEvents) {
1250fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                // Send a down event first, as we dropped a bunch of sudden jumps and assume that
1260fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                // the user is releasing the touch on the second key.
1270fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
1280fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                        MotionEvent.ACTION_DOWN,
1290fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                        x, y, me.getMetaState());
130c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka                mView.processMotionEvent(translated);
1310fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                translated.recycle();
1320fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                mDroppingEvents = false;
1330fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani                // Let the up event get processed as well, result = false
1340fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            }
1350fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani            break;
1360fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        }
1370fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        // Track the previous coordinate
1380fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        mLastX = x;
1390fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        mLastY = y;
1400fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani        return result;
1410fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani    }
1420fef498a07515bdd5ac1ccfa564776d72fd85a51Amith Yamasani
143358e485b14938fbcb5af5be75aa29f2b73674100Amith Yamasani    public boolean onTouchEvent(MotionEvent me) {
144d2a431efa726771dee5c7b90004a0ed670d9a129Tadashi G. Takaoka        // If there was a sudden jump, return without processing the actual motion event.
145c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        if (handleSuddenJumping(me)) {
146faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka            if (DEBUG_MODE)
147faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
1489bc29d78a6ce83f77869aa63748176241e29d43cKurt Partridge            if (ProductionFlag.IS_EXPERIMENTAL) {
1499bc29d78a6ce83f77869aa63748176241e29d43cKurt Partridge                ResearchLogger.suddenJumpingTouchEventHandler_onTouchEvent(me);
1509bc29d78a6ce83f77869aa63748176241e29d43cKurt Partridge            }
151faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka            return true;
152faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka        }
153c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        return mView.processMotionEvent(me);
154bad436e93b49116f9005433845bf53126452a839Amith Yamasani    }
155923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
156