1/*
2 * Copyright (C) 2013 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
17package com.android.inputmethod.keyboard.internal;
18
19import android.content.res.Resources;
20import android.util.DisplayMetrics;
21import android.util.Log;
22
23import com.android.inputmethod.latin.R;
24import com.android.inputmethod.latin.common.Constants;
25import com.android.inputmethod.latin.define.DebugFlags;
26
27// This hack is applied to certain classes of tablets.
28public final class BogusMoveEventDetector {
29    private static final String TAG = BogusMoveEventDetector.class.getSimpleName();
30    private static final boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED;
31
32    // Move these thresholds to resource.
33    // These thresholds' unit is a diagonal length of a key.
34    private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f;
35    private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.14f;
36
37    private static boolean sNeedsProximateBogusDownMoveUpEventHack;
38
39    public static void init(final Resources res) {
40        // The proximate bogus down move up event hack is needed for a device such like,
41        // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi.
42        // Though it seems odd to use screen density as criteria of the quality of the touch
43        // screen, the small table that has a less density screen than hdpi most likely has been
44        // made with the touch screen that needs the hack.
45        final int screenMetrics = res.getInteger(R.integer.config_screen_metrics);
46        final boolean isLargeTablet = (screenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET);
47        final boolean isSmallTablet = (screenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET);
48        final int densityDpi = res.getDisplayMetrics().densityDpi;
49        final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH);
50        final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen);
51        if (DEBUG_MODE) {
52            final int sw = res.getConfiguration().smallestScreenWidthDp;
53            Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack
54                    + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi
55                    + " screenMetrics=" + screenMetrics);
56        }
57        sNeedsProximateBogusDownMoveUpEventHack = needsTheHack;
58    }
59
60    private int mAccumulatedDistanceThreshold;
61    private int mRadiusThreshold;
62
63    // Accumulated distance from actual and artificial down keys.
64    /* package */ int mAccumulatedDistanceFromDownKey;
65    private int mActualDownX;
66    private int mActualDownY;
67
68    public void setKeyboardGeometry(final int keyWidth, final int keyHeight) {
69        final float keyDiagonal = (float)Math.hypot(keyWidth, keyHeight);
70        mAccumulatedDistanceThreshold = (int)(
71                keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD);
72        mRadiusThreshold = (int)(keyDiagonal * BOGUS_MOVE_RADIUS_THRESHOLD);
73    }
74
75    public void onActualDownEvent(final int x, final int y) {
76        mActualDownX = x;
77        mActualDownY = y;
78    }
79
80    public void onDownKey() {
81        mAccumulatedDistanceFromDownKey = 0;
82    }
83
84    public void onMoveKey(final int distance) {
85        mAccumulatedDistanceFromDownKey += distance;
86    }
87
88    public boolean hasTraveledLongDistance(final int x, final int y) {
89        if (!sNeedsProximateBogusDownMoveUpEventHack) {
90            return false;
91        }
92        final int dx = Math.abs(x - mActualDownX);
93        final int dy = Math.abs(y - mActualDownY);
94        // A bogus move event should be a horizontal movement. A vertical movement might be
95        // a sloppy typing and should be ignored.
96        return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold;
97    }
98
99    public int getAccumulatedDistanceFromDownKey() {
100        return mAccumulatedDistanceFromDownKey;
101    }
102
103    public int getDistanceFromDownEvent(final int x, final int y) {
104        return getDistance(x, y, mActualDownX, mActualDownY);
105    }
106
107    private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
108        return (int)Math.hypot(x1 - x2, y1 - y2);
109    }
110
111    public boolean isCloseToActualDownEvent(final int x, final int y) {
112        return sNeedsProximateBogusDownMoveUpEventHack
113                && getDistanceFromDownEvent(x, y) < mRadiusThreshold;
114    }
115}
116