1 /* 2 * Copyright (C) 2011 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; 18 19 import android.graphics.Rect; 20 import android.text.TextUtils; 21 import android.util.Log; 22 23 import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; 24 import com.android.inputmethod.latin.Constants; 25 import com.android.inputmethod.latin.JniUtils; 26 27 import java.util.Arrays; 28 29 public class ProximityInfo { 30 private static final String TAG = ProximityInfo.class.getSimpleName(); 31 private static final boolean DEBUG = false; 32 33 // Must be equal to MAX_PROXIMITY_CHARS_SIZE in native/jni/src/defines.h 34 public static final int MAX_PROXIMITY_CHARS_SIZE = 16; 35 /** Number of key widths from current touch point to search for nearest keys. */ 36 private static final float SEARCH_DISTANCE = 1.2f; 37 private static final Key[] EMPTY_KEY_ARRAY = new Key[0]; 38 private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f; 39 40 private final int mGridWidth; 41 private final int mGridHeight; 42 private final int mGridSize; 43 private final int mCellWidth; 44 private final int mCellHeight; 45 // TODO: Find a proper name for mKeyboardMinWidth 46 private final int mKeyboardMinWidth; 47 private final int mKeyboardHeight; 48 private final int mMostCommonKeyWidth; 49 private final int mMostCommonKeyHeight; 50 private final Key[] mKeys; 51 private final Key[][] mGridNeighbors; 52 private final String mLocaleStr; 53 54 ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight, 55 final int minWidth, final int height, final int mostCommonKeyWidth, 56 final int mostCommonKeyHeight, final Key[] keys, 57 final TouchPositionCorrection touchPositionCorrection) { 58 if (TextUtils.isEmpty(localeStr)) { 59 mLocaleStr = ""; 60 } else { 61 mLocaleStr = localeStr; 62 } 63 mGridWidth = gridWidth; 64 mGridHeight = gridHeight; 65 mGridSize = mGridWidth * mGridHeight; 66 mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth; 67 mCellHeight = (height + mGridHeight - 1) / mGridHeight; 68 mKeyboardMinWidth = minWidth; 69 mKeyboardHeight = height; 70 mMostCommonKeyHeight = mostCommonKeyHeight; 71 mMostCommonKeyWidth = mostCommonKeyWidth; 72 mKeys = keys; 73 mGridNeighbors = new Key[mGridSize][]; 74 if (minWidth == 0 || height == 0) { 75 // No proximity required. Keyboard might be more keys keyboard. 76 return; 77 } 78 computeNearestNeighbors(); 79 mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); 80 } 81 82 private long mNativeProximityInfo; 83 static { 84 JniUtils.loadNativeLibrary(); 85 } 86 87 // TODO: Stop passing proximityCharsArray 88 private static native long setProximityInfoNative(String locale, 89 int displayWidth, int displayHeight, int gridWidth, int gridHeight, 90 int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray, 91 int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths, 92 int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs, 93 float[] sweetSpotCenterYs, float[] sweetSpotRadii); 94 95 private static native void releaseProximityInfoNative(long nativeProximityInfo); 96 97 private static boolean needsProximityInfo(final Key key) { 98 // Don't include special keys into ProximityInfo. 99 return key.mCode >= Constants.CODE_SPACE; 100 } 101 102 private static int getProximityInfoKeysCount(final Key[] keys) { 103 int count = 0; 104 for (final Key key : keys) { 105 if (needsProximityInfo(key)) { 106 count++; 107 } 108 } 109 return count; 110 } 111 112 private long createNativeProximityInfo(final TouchPositionCorrection touchPositionCorrection) { 113 final Key[][] gridNeighborKeys = mGridNeighbors; 114 final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; 115 Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE); 116 for (int i = 0; i < mGridSize; ++i) { 117 final int proximityCharsLength = gridNeighborKeys[i].length; 118 int infoIndex = i * MAX_PROXIMITY_CHARS_SIZE; 119 for (int j = 0; j < proximityCharsLength; ++j) { 120 final Key neighborKey = gridNeighborKeys[i][j]; 121 // Excluding from proximityCharsArray 122 if (!needsProximityInfo(neighborKey)) { 123 continue; 124 } 125 proximityCharsArray[infoIndex] = neighborKey.mCode; 126 infoIndex++; 127 } 128 } 129 if (DEBUG) { 130 final StringBuilder sb = new StringBuilder(); 131 for (int i = 0; i < mGridSize; i++) { 132 sb.setLength(0); 133 for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; j++) { 134 final int code = proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j]; 135 if (code == Constants.NOT_A_CODE) { 136 break; 137 } 138 if (sb.length() > 0) sb.append(" "); 139 sb.append(Constants.printableCode(code)); 140 } 141 Log.d(TAG, "proxmityChars["+i+"]: " + sb); 142 } 143 } 144 145 final Key[] keys = mKeys; 146 final int keyCount = getProximityInfoKeysCount(keys); 147 final int[] keyXCoordinates = new int[keyCount]; 148 final int[] keyYCoordinates = new int[keyCount]; 149 final int[] keyWidths = new int[keyCount]; 150 final int[] keyHeights = new int[keyCount]; 151 final int[] keyCharCodes = new int[keyCount]; 152 final float[] sweetSpotCenterXs; 153 final float[] sweetSpotCenterYs; 154 final float[] sweetSpotRadii; 155 156 for (int infoIndex = 0, keyIndex = 0; keyIndex < keys.length; keyIndex++) { 157 final Key key = keys[keyIndex]; 158 // Excluding from key coordinate arrays 159 if (!needsProximityInfo(key)) { 160 continue; 161 } 162 keyXCoordinates[infoIndex] = key.mX; 163 keyYCoordinates[infoIndex] = key.mY; 164 keyWidths[infoIndex] = key.mWidth; 165 keyHeights[infoIndex] = key.mHeight; 166 keyCharCodes[infoIndex] = key.mCode; 167 infoIndex++; 168 } 169 170 if (touchPositionCorrection != null && touchPositionCorrection.isValid()) { 171 if (DEBUG) { 172 Log.d(TAG, "touchPositionCorrection: ON"); 173 } 174 sweetSpotCenterXs = new float[keyCount]; 175 sweetSpotCenterYs = new float[keyCount]; 176 sweetSpotRadii = new float[keyCount]; 177 final int rows = touchPositionCorrection.getRows(); 178 final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS 179 * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight); 180 for (int infoIndex = 0, keyIndex = 0; keyIndex < keys.length; keyIndex++) { 181 final Key key = keys[keyIndex]; 182 // Excluding from touch position correction arrays 183 if (!needsProximityInfo(key)) { 184 continue; 185 } 186 final Rect hitBox = key.mHitBox; 187 sweetSpotCenterXs[infoIndex] = hitBox.exactCenterX(); 188 sweetSpotCenterYs[infoIndex] = hitBox.exactCenterY(); 189 sweetSpotRadii[infoIndex] = defaultRadius; 190 final int row = hitBox.top / mMostCommonKeyHeight; 191 if (row < rows) { 192 final int hitBoxWidth = hitBox.width(); 193 final int hitBoxHeight = hitBox.height(); 194 final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight); 195 sweetSpotCenterXs[infoIndex] += 196 touchPositionCorrection.getX(row) * hitBoxWidth; 197 sweetSpotCenterYs[infoIndex] += 198 touchPositionCorrection.getY(row) * hitBoxHeight; 199 sweetSpotRadii[infoIndex] = 200 touchPositionCorrection.getRadius(row) * hitBoxDiagonal; 201 } 202 if (DEBUG) { 203 Log.d(TAG, String.format( 204 " [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", infoIndex, row, 205 sweetSpotCenterXs[infoIndex], sweetSpotCenterYs[infoIndex], 206 sweetSpotRadii[infoIndex], (row < rows ? "correct" : "default"), 207 Constants.printableCode(key.mCode))); 208 } 209 infoIndex++; 210 } 211 } else { 212 sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null; 213 if (DEBUG) { 214 Log.d(TAG, "touchPositionCorrection: OFF"); 215 } 216 } 217 218 // TODO: Stop passing proximityCharsArray 219 return setProximityInfoNative(mLocaleStr, mKeyboardMinWidth, mKeyboardHeight, 220 mGridWidth, mGridHeight, mMostCommonKeyWidth, mMostCommonKeyHeight, 221 proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths, 222 keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); 223 } 224 225 public long getNativeProximityInfo() { 226 return mNativeProximityInfo; 227 } 228 229 @Override 230 protected void finalize() throws Throwable { 231 try { 232 if (mNativeProximityInfo != 0) { 233 releaseProximityInfoNative(mNativeProximityInfo); 234 mNativeProximityInfo = 0; 235 } 236 } finally { 237 super.finalize(); 238 } 239 } 240 241 private void computeNearestNeighbors() { 242 final int defaultWidth = mMostCommonKeyWidth; 243 final Key[] keys = mKeys; 244 final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE); 245 final int threshold = thresholdBase * thresholdBase; 246 // Round-up so we don't have any pixels outside the grid 247 final Key[] neighborKeys = new Key[keys.length]; 248 final int gridWidth = mGridWidth * mCellWidth; 249 final int gridHeight = mGridHeight * mCellHeight; 250 for (int x = 0; x < gridWidth; x += mCellWidth) { 251 for (int y = 0; y < gridHeight; y += mCellHeight) { 252 final int centerX = x + mCellWidth / 2; 253 final int centerY = y + mCellHeight / 2; 254 int count = 0; 255 for (final Key key : keys) { 256 if (key.isSpacer()) continue; 257 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) { 258 neighborKeys[count++] = key; 259 } 260 } 261 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = 262 Arrays.copyOfRange(neighborKeys, 0, count); 263 } 264 } 265 } 266 267 public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode, 268 final int[] dest) { 269 final int destLength = dest.length; 270 if (destLength < 1) { 271 return; 272 } 273 int index = 0; 274 if (primaryKeyCode > Constants.CODE_SPACE) { 275 dest[index++] = primaryKeyCode; 276 } 277 final Key[] nearestKeys = getNearestKeys(x, y); 278 for (Key key : nearestKeys) { 279 if (index >= destLength) { 280 break; 281 } 282 final int code = key.mCode; 283 if (code <= Constants.CODE_SPACE) { 284 break; 285 } 286 dest[index++] = code; 287 } 288 if (index < destLength) { 289 dest[index] = Constants.NOT_A_CODE; 290 } 291 } 292 293 public Key[] getNearestKeys(final int x, final int y) { 294 if (mGridNeighbors == null) { 295 return EMPTY_KEY_ARRAY; 296 } 297 if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { 298 int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); 299 if (index < mGridSize) { 300 return mGridNeighbors[index]; 301 } 302 } 303 return EMPTY_KEY_ARRAY; 304 } 305 } 306