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