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