ProximityInfo.java revision b4fbbe57f574ce6e6a5827156f875fe7d3eb5089
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.JniUtils; 24import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; 25 26import java.util.Arrays; 27import java.util.Collections; 28import java.util.HashMap; 29import java.util.List; 30import java.util.Map; 31 32public class ProximityInfo { 33 public static final int MAX_PROXIMITY_CHARS_SIZE = 16; 34 /** Number of key widths from current touch point to search for nearest keys. */ 35 private static float SEARCH_DISTANCE = 1.2f; 36 private static final Key[] EMPTY_KEY_ARRAY = new Key[0]; 37 38 private final int mKeyHeight; 39 private final int mGridWidth; 40 private final int mGridHeight; 41 private final int mGridSize; 42 private final int mCellWidth; 43 private final int mCellHeight; 44 // TODO: Find a proper name for mKeyboardMinWidth 45 private final int mKeyboardMinWidth; 46 private final int mKeyboardHeight; 47 private final int mMostCommonKeyWidth; 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 Map<Integer, List<Integer>> additionalProximityChars) { 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 mKeyHeight = mostCommonKeyHeight; 68 mMostCommonKeyWidth = mostCommonKeyWidth; 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 mostCommonKeyWidth, keys, touchPositionCorrection, additionalProximityChars); 76 } 77 78 public static ProximityInfo createDummyProximityInfo() { 79 return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null, 80 Collections.<Integer, List<Integer>> emptyMap()); 81 } 82 83 public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) { 84 final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); 85 spellCheckerProximityInfo.mNativeProximityInfo = 86 spellCheckerProximityInfo.setProximityInfoNative("", 87 SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, (480 / 10), proximity, 88 0, null, null, null, null, null, null, null, null); 89 return spellCheckerProximityInfo; 90 } 91 92 private long mNativeProximityInfo; 93 static { 94 JniUtils.loadNativeLibrary(); 95 } 96 97 private native long setProximityInfoNative( 98 String locale, int maxProximityCharsSize, int displayWidth, 99 int displayHeight, int gridWidth, int gridHeight, 100 int mostCommonKeyWidth, int[] proximityCharsArray, 101 int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, 102 int[] keyWidths, int[] keyHeights, int[] keyCharCodes, 103 float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii); 104 105 private native void releaseProximityInfoNative(long nativeProximityInfo); 106 107 private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth, 108 int keyboardHeight, final Key[] keys, TouchPositionCorrection touchPositionCorrection) { 109 final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; 110 Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE); 111 for (int i = 0; i < mGridSize; ++i) { 112 final int proximityCharsLength = gridNeighborKeys[i].length; 113 for (int j = 0; j < proximityCharsLength; ++j) { 114 proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = 115 gridNeighborKeys[i][j].mCode; 116 } 117 } 118 final int keyCount = keys.length; 119 final int[] keyXCoordinates = new int[keyCount]; 120 final int[] keyYCoordinates = new int[keyCount]; 121 final int[] keyWidths = new int[keyCount]; 122 final int[] keyHeights = new int[keyCount]; 123 final int[] keyCharCodes = new int[keyCount]; 124 final float[] sweetSpotCenterXs; 125 final float[] sweetSpotCenterYs; 126 final float[] sweetSpotRadii; 127 final boolean calculateSweetSpotParams; 128 if (touchPositionCorrection != null && touchPositionCorrection.isValid()) { 129 sweetSpotCenterXs = new float[keyCount]; 130 sweetSpotCenterYs = new float[keyCount]; 131 sweetSpotRadii = new float[keyCount]; 132 calculateSweetSpotParams = true; 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 if (calculateSweetSpotParams) { 141 final Rect hitBox = key.mHitBox; 142 final int row = hitBox.top / mKeyHeight; 143 if (row < touchPositionCorrection.mRadii.length) { 144 final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f; 145 final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f; 146 final float hitBoxWidth = hitBox.right - hitBox.left; 147 final float hitBoxHeight = hitBox.bottom - hitBox.top; 148 final float x = touchPositionCorrection.mXs[row]; 149 final float y = touchPositionCorrection.mYs[row]; 150 final float radius = touchPositionCorrection.mRadii[row]; 151 sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth; 152 sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight; 153 sweetSpotRadii[i] = radius * (float) Math.sqrt( 154 hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight); 155 } 156 } 157 } 158 } else { 159 sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null; 160 calculateSweetSpotParams = false; 161 } 162 163 mNativeProximityInfo = setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE, 164 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, 165 proximityCharsArray, 166 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, 167 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); 168 } 169 170 public long getNativeProximityInfo() { 171 return mNativeProximityInfo; 172 } 173 174 @Override 175 protected void finalize() throws Throwable { 176 try { 177 if (mNativeProximityInfo != 0) { 178 releaseProximityInfoNative(mNativeProximityInfo); 179 mNativeProximityInfo = 0; 180 } 181 } finally { 182 super.finalize(); 183 } 184 } 185 186 private void computeNearestNeighbors(int defaultWidth, final Key[] keys, 187 TouchPositionCorrection touchPositionCorrection, 188 Map<Integer, List<Integer>> additionalProximityChars) { 189 final HashMap<Integer, Key> keyCodeMap = new HashMap<Integer, Key>(); 190 for (final Key key : keys) { 191 keyCodeMap.put(key.mCode, key); 192 } 193 final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE); 194 final int threshold = thresholdBase * thresholdBase; 195 // Round-up so we don't have any pixels outside the grid 196 final Key[] neighborKeys = new Key[keys.length]; 197 final int gridWidth = mGridWidth * mCellWidth; 198 final int gridHeight = mGridHeight * mCellHeight; 199 for (int x = 0; x < gridWidth; x += mCellWidth) { 200 for (int y = 0; y < gridHeight; y += mCellHeight) { 201 final int centerX = x + mCellWidth / 2; 202 final int centerY = y + mCellHeight / 2; 203 int count = 0; 204 for (final Key key : keys) { 205 if (key.isSpacer()) continue; 206 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) { 207 neighborKeys[count++] = key; 208 } 209 } 210 int currentCodesSize = count; 211 for (int i = 0; i < currentCodesSize; ++i) { 212 final int c = neighborKeys[i].mCode; 213 final List<Integer> additionalChars = additionalProximityChars.get(c); 214 if (additionalChars == null || additionalChars.size() == 0) { 215 continue; 216 } 217 for (int j = 0; j < additionalChars.size(); ++j) { 218 final int additionalChar = additionalChars.get(j); 219 boolean contains = false; 220 for (int k = 0; k < count; ++k) { 221 if(additionalChar == neighborKeys[k].mCode) { 222 contains = true; 223 break; 224 } 225 } 226 if (!contains) { 227 neighborKeys[count++] = keyCodeMap.get(additionalChar); 228 } 229 } 230 } 231 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = 232 Arrays.copyOfRange(neighborKeys, 0, count); 233 } 234 } 235 setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys, 236 touchPositionCorrection); 237 } 238 239 public Key[] getNearestKeys(int x, int y) { 240 if (mGridNeighbors == null) { 241 return EMPTY_KEY_ARRAY; 242 } 243 if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { 244 int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); 245 if (index < mGridSize) { 246 return mGridNeighbors[index]; 247 } 248 } 249 return EMPTY_KEY_ARRAY; 250 } 251} 252