• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 
17 package com.android.inputmethod.keyboard;
18 
19 import android.util.Log;
20 
21 import java.util.Arrays;
22 import java.util.List;
23 
24 public class KeyDetector {
25     private static final String TAG = KeyDetector.class.getSimpleName();
26     private static final boolean DEBUG = false;
27 
28     public static final int NOT_A_CODE = -1;
29     public static final int NOT_A_KEY = -1;
30 
31     private final int mKeyHysteresisDistanceSquared;
32 
33     private Keyboard mKeyboard;
34     private int mCorrectionX;
35     private int mCorrectionY;
36     private boolean mProximityCorrectOn;
37     private int mProximityThresholdSquare;
38 
39     // working area
40     private static final int MAX_NEARBY_KEYS = 12;
41     private final int[] mDistances = new int[MAX_NEARBY_KEYS];
42     private final int[] mIndices = new int[MAX_NEARBY_KEYS];
43 
44     /**
45      * This class handles key detection.
46      *
47      * @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
48      * movement will not been handled as meaningful movement. The unit is pixel.
49      */
KeyDetector(float keyHysteresisDistance)50     public KeyDetector(float keyHysteresisDistance) {
51         mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
52     }
53 
setKeyboard(Keyboard keyboard, float correctionX, float correctionY)54     public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
55         if (keyboard == null)
56             throw new NullPointerException();
57         mCorrectionX = (int)correctionX;
58         mCorrectionY = (int)correctionY;
59         mKeyboard = keyboard;
60         final int threshold = keyboard.mMostCommonKeyWidth;
61         mProximityThresholdSquare = threshold * threshold;
62     }
63 
getKeyHysteresisDistanceSquared()64     public int getKeyHysteresisDistanceSquared() {
65         return mKeyHysteresisDistanceSquared;
66     }
67 
getTouchX(int x)68     protected int getTouchX(int x) {
69         return x + mCorrectionX;
70     }
71 
getTouchY(int y)72     protected int getTouchY(int y) {
73         return y + mCorrectionY;
74     }
75 
getKeyboard()76     public Keyboard getKeyboard() {
77         if (mKeyboard == null)
78             throw new IllegalStateException("keyboard isn't set");
79         return mKeyboard;
80     }
81 
setProximityCorrectionEnabled(boolean enabled)82     public void setProximityCorrectionEnabled(boolean enabled) {
83         mProximityCorrectOn = enabled;
84     }
85 
isProximityCorrectionEnabled()86     public boolean isProximityCorrectionEnabled() {
87         return mProximityCorrectOn;
88     }
89 
setProximityThreshold(int threshold)90     public void setProximityThreshold(int threshold) {
91         mProximityThresholdSquare = threshold * threshold;
92     }
93 
alwaysAllowsSlidingInput()94     public boolean alwaysAllowsSlidingInput() {
95         return false;
96     }
97 
98     /**
99      * Computes maximum size of the array that can contain all nearby key indices returned by
100      * {@link #getKeyIndexAndNearbyCodes}.
101      *
102      * @return Returns maximum size of the array that can contain all nearby key indices returned
103      *         by {@link #getKeyIndexAndNearbyCodes}.
104      */
getMaxNearbyKeys()105     protected int getMaxNearbyKeys() {
106         return MAX_NEARBY_KEYS;
107     }
108 
109     /**
110      * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
111      * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
112      *
113      * @return Allocates and returns an array that can hold all key indices returned by
114      *         {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
115      *         initialized by {@link #NOT_A_CODE} value.
116      */
newCodeArray()117     public int[] newCodeArray() {
118         int[] codes = new int[getMaxNearbyKeys()];
119         Arrays.fill(codes, NOT_A_CODE);
120         return codes;
121     }
122 
initializeNearbyKeys()123     private void initializeNearbyKeys() {
124         Arrays.fill(mDistances, Integer.MAX_VALUE);
125         Arrays.fill(mIndices, NOT_A_KEY);
126     }
127 
128     /**
129      * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
130      * If the distance of two keys are the same, the key which the point is on should be considered
131      * as a closer one.
132      *
133      * @param keyIndex index of the key.
134      * @param distance distance between the key's edge and user touched point.
135      * @param isOnKey true if the point is on the key.
136      * @return order of the key in the nearby buffer, 0 if it is the nearest key.
137      */
sortNearbyKeys(int keyIndex, int distance, boolean isOnKey)138     private int sortNearbyKeys(int keyIndex, int distance, boolean isOnKey) {
139         final int[] distances = mDistances;
140         final int[] indices = mIndices;
141         for (int insertPos = 0; insertPos < distances.length; insertPos++) {
142             final int comparingDistance = distances[insertPos];
143             if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
144                 final int nextPos = insertPos + 1;
145                 if (nextPos < distances.length) {
146                     System.arraycopy(distances, insertPos, distances, nextPos,
147                             distances.length - nextPos);
148                     System.arraycopy(indices, insertPos, indices, nextPos,
149                             indices.length - nextPos);
150                 }
151                 distances[insertPos] = distance;
152                 indices[insertPos] = keyIndex;
153                 return insertPos;
154             }
155         }
156         return distances.length;
157     }
158 
getNearbyKeyCodes(final int[] allCodes)159     private void getNearbyKeyCodes(final int[] allCodes) {
160         final List<Key> keys = getKeyboard().mKeys;
161         final int[] indices = mIndices;
162 
163         // allCodes[0] should always have the key code even if it is a non-letter key.
164         if (indices[0] == NOT_A_KEY) {
165             allCodes[0] = NOT_A_CODE;
166             return;
167         }
168 
169         int numCodes = 0;
170         for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
171             final int index = indices[j];
172             if (index == NOT_A_KEY)
173                 break;
174             final int code = keys.get(index).mCode;
175             // filter out a non-letter key from nearby keys
176             if (code < Keyboard.CODE_SPACE)
177                 continue;
178             allCodes[numCodes++] = code;
179         }
180     }
181 
182     /**
183      * Finds all possible nearby key indices around a touch event point and returns the nearest key
184      * index. The algorithm to determine the nearby keys depends on the threshold set by
185      * {@link #setProximityThreshold(int)} and the mode set by
186      * {@link #setProximityCorrectionEnabled(boolean)}.
187      *
188      * @param x The x-coordinate of a touch point
189      * @param y The y-coordinate of a touch point
190      * @param allCodes All nearby key code except functional key are returned in this array
191      * @return The nearest key index
192      */
getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes)193     public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
194         final List<Key> keys = getKeyboard().mKeys;
195         final int touchX = getTouchX(x);
196         final int touchY = getTouchY(y);
197 
198         initializeNearbyKeys();
199         int primaryIndex = NOT_A_KEY;
200         for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
201             final Key key = keys.get(index);
202             final boolean isOnKey = key.isOnKey(touchX, touchY);
203             final int distance = key.squaredDistanceToEdge(touchX, touchY);
204             if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
205                 final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
206                 if (insertedPosition == 0 && isOnKey)
207                     primaryIndex = index;
208             }
209         }
210 
211         if (allCodes != null && allCodes.length > 0) {
212             getNearbyKeyCodes(allCodes);
213             if (DEBUG) {
214                 Log.d(TAG, "x=" + x + " y=" + y
215                         + " primary="
216                         + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
217                         + " codes=" + Arrays.toString(allCodes));
218             }
219         }
220 
221         return primaryIndex;
222     }
223 }
224