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 22 import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; 23 import com.android.inputmethod.latin.Constants; 24 import com.android.inputmethod.latin.JniUtils; 25 26 import java.util.Arrays; 27 28 public final 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 private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f; 36 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 int mMostCommonKeyHeight; 47 private final Key[] mKeys; 48 private final Key[][] mGridNeighbors; 49 private final String mLocaleStr; 50 51 ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight, 52 final int minWidth, final int height, final int mostCommonKeyWidth, 53 final int mostCommonKeyHeight, final Key[] keys, 54 final TouchPositionCorrection touchPositionCorrection) { 55 if (TextUtils.isEmpty(localeStr)) { 56 mLocaleStr = ""; 57 } else { 58 mLocaleStr = localeStr; 59 } 60 mGridWidth = gridWidth; 61 mGridHeight = gridHeight; 62 mGridSize = mGridWidth * mGridHeight; 63 mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth; 64 mCellHeight = (height + mGridHeight - 1) / mGridHeight; 65 mKeyboardMinWidth = minWidth; 66 mKeyboardHeight = height; 67 mMostCommonKeyHeight = mostCommonKeyHeight; 68 mMostCommonKeyWidth = mostCommonKeyWidth; 69 mKeys = keys; 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(touchPositionCorrection); 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 final int rowSize, final int gridWidth, final 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 TouchPositionCorrection touchPositionCorrection) { 110 final Key[][] gridNeighborKeys = mGridNeighbors; 111 final int keyboardWidth = mKeyboardMinWidth; 112 final int keyboardHeight = mKeyboardHeight; 113 final Key[] keys = mKeys; 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 final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS 147 * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight); 148 for (int i = 0; i < keyCount; i++) { 149 final Key key = keys[i]; 150 final Rect hitBox = key.mHitBox; 151 sweetSpotCenterXs[i] = hitBox.exactCenterX(); 152 sweetSpotCenterYs[i] = hitBox.exactCenterY(); 153 sweetSpotRadii[i] = defaultRadius; 154 final int row = hitBox.top / mMostCommonKeyHeight; 155 if (row < touchPositionCorrection.getRows()) { 156 final int hitBoxWidth = hitBox.width(); 157 final int hitBoxHeight = hitBox.height(); 158 final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight); 159 sweetSpotCenterXs[i] += touchPositionCorrection.getX(row) * hitBoxWidth; 160 sweetSpotCenterYs[i] += touchPositionCorrection.getY(row) * hitBoxHeight; 161 sweetSpotRadii[i] = touchPositionCorrection.getRadius(row) * hitBoxDiagonal; 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(final int x, final int y, final int primaryKeyCode, 218 final int[] dest) { 219 final int destLength = dest.length; 220 if (destLength < 1) { 221 return; 222 } 223 int index = 0; 224 if (primaryKeyCode > Keyboard.CODE_SPACE) { 225 dest[index++] = primaryKeyCode; 226 } 227 final Key[] nearestKeys = getNearestKeys(x, y); 228 for (Key key : nearestKeys) { 229 if (index >= destLength) { 230 break; 231 } 232 final int code = key.mCode; 233 if (code <= Keyboard.CODE_SPACE) { 234 break; 235 } 236 dest[index++] = code; 237 } 238 if (index < destLength) { 239 dest[index] = Constants.NOT_A_CODE; 240 } 241 } 242 243 public Key[] getNearestKeys(final int x, final int y) { 244 if (mGridNeighbors == null) { 245 return EMPTY_KEY_ARRAY; 246 } 247 if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { 248 int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); 249 if (index < mGridSize) { 250 return mGridNeighbors[index]; 251 } 252 } 253 return EMPTY_KEY_ARRAY; 254 } 255 } 256