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