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 
17 package com.android.inputmethod.keyboard.internal;
18 
19 import android.content.res.Resources;
20 import android.util.DisplayMetrics;
21 import android.util.Log;
22 
23 import com.android.inputmethod.latin.R;
24 import com.android.inputmethod.latin.common.Constants;
25 import com.android.inputmethod.latin.define.DebugFlags;
26 
27 // This hack is applied to certain classes of tablets.
28 public 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 
init(final Resources res)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 
setKeyboardGeometry(final int keyWidth, final int keyHeight)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 
onActualDownEvent(final int x, final int y)75     public void onActualDownEvent(final int x, final int y) {
76         mActualDownX = x;
77         mActualDownY = y;
78     }
79 
onDownKey()80     public void onDownKey() {
81         mAccumulatedDistanceFromDownKey = 0;
82     }
83 
onMoveKey(final int distance)84     public void onMoveKey(final int distance) {
85         mAccumulatedDistanceFromDownKey += distance;
86     }
87 
hasTraveledLongDistance(final int x, final int y)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 
getAccumulatedDistanceFromDownKey()99     public int getAccumulatedDistanceFromDownKey() {
100         return mAccumulatedDistanceFromDownKey;
101     }
102 
getDistanceFromDownEvent(final int x, final int y)103     public int getDistanceFromDownEvent(final int x, final int y) {
104         return getDistance(x, y, mActualDownX, mActualDownY);
105     }
106 
getDistance(final int x1, final int y1, final int x2, final int y2)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 
isCloseToActualDownEvent(final int x, final int y)111     public boolean isCloseToActualDownEvent(final int x, final int y) {
112         return sNeedsProximateBogusDownMoveUpEventHack
113                 && getDistanceFromDownEvent(x, y) < mRadiusThreshold;
114     }
115 }
116