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