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