ProximityInfo.java revision 552c3c27f04e6769e40cffbce3a9e8eed1269294
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; 20 21import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection; 22import com.android.inputmethod.latin.JniUtils; 23import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; 24 25import java.util.Arrays; 26import java.util.Collections; 27import java.util.HashMap; 28import java.util.List; 29import java.util.Map; 30import java.util.Set; 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 50 ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int mostCommonKeyWidth, 51 int mostCommonKeyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection, 52 Map<Integer, List<Integer>> additionalProximityChars) { 53 mGridWidth = gridWidth; 54 mGridHeight = gridHeight; 55 mGridSize = mGridWidth * mGridHeight; 56 mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth; 57 mCellHeight = (height + mGridHeight - 1) / mGridHeight; 58 mKeyboardMinWidth = minWidth; 59 mKeyboardHeight = height; 60 mKeyHeight = mostCommonKeyHeight; 61 mMostCommonKeyWidth = mostCommonKeyWidth; 62 mGridNeighbors = new Key[mGridSize][]; 63 if (minWidth == 0 || height == 0) { 64 // No proximity required. Keyboard might be more keys keyboard. 65 return; 66 } 67 computeNearestNeighbors( 68 mostCommonKeyWidth, keys, touchPositionCorrection, additionalProximityChars); 69 } 70 71 public static ProximityInfo createDummyProximityInfo() { 72 return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key> emptySet(), 73 null, Collections.<Integer, List<Integer>> emptyMap()); 74 } 75 76 public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) { 77 final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); 78 spellCheckerProximityInfo.mNativeProximityInfo = 79 spellCheckerProximityInfo.setProximityInfoNative("", 80 SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, (480 / 10), proximity, 81 0, null, null, null, null, null, null, null, null); 82 return spellCheckerProximityInfo; 83 } 84 85 private long mNativeProximityInfo; 86 static { 87 JniUtils.loadNativeLibrary(); 88 } 89 90 private native long setProximityInfoNative( 91 String locale, int maxProximityCharsSize, int displayWidth, 92 int displayHeight, int gridWidth, int gridHeight, 93 int mostCommonKeyWidth, int[] proximityCharsArray, 94 int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, 95 int[] keyWidths, int[] keyHeights, int[] keyCharCodes, 96 float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii); 97 98 private native void releaseProximityInfoNative(long nativeProximityInfo); 99 100 private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth, 101 int keyboardHeight, Set<Key> keys, 102 TouchPositionCorrection touchPositionCorrection) { 103 final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; 104 Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE); 105 for (int i = 0; i < mGridSize; ++i) { 106 final int proximityCharsLength = gridNeighborKeys[i].length; 107 for (int j = 0; j < proximityCharsLength; ++j) { 108 proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = 109 gridNeighborKeys[i][j].mCode; 110 } 111 } 112 final int keyCount = keys.size(); 113 final int[] keyXCoordinates = new int[keyCount]; 114 final int[] keyYCoordinates = new int[keyCount]; 115 final int[] keyWidths = new int[keyCount]; 116 final int[] keyHeights = new int[keyCount]; 117 final int[] keyCharCodes = new int[keyCount]; 118 final float[] sweetSpotCenterXs; 119 final float[] sweetSpotCenterYs; 120 final float[] sweetSpotRadii; 121 final boolean calculateSweetSpotParams; 122 if (touchPositionCorrection != null && touchPositionCorrection.isValid()) { 123 sweetSpotCenterXs = new float[keyCount]; 124 sweetSpotCenterYs = new float[keyCount]; 125 sweetSpotRadii = new float[keyCount]; 126 calculateSweetSpotParams = true; 127 int i = 0; 128 for (final Key key : keys) { 129 keyXCoordinates[i] = key.mX; 130 keyYCoordinates[i] = key.mY; 131 keyWidths[i] = key.mWidth; 132 keyHeights[i] = key.mHeight; 133 keyCharCodes[i] = key.mCode; 134 if (calculateSweetSpotParams) { 135 final Rect hitBox = key.mHitBox; 136 final int row = hitBox.top / mKeyHeight; 137 if (row < touchPositionCorrection.mRadii.length) { 138 final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f; 139 final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f; 140 final float hitBoxWidth = hitBox.right - hitBox.left; 141 final float hitBoxHeight = hitBox.bottom - hitBox.top; 142 final float x = touchPositionCorrection.mXs[row]; 143 final float y = touchPositionCorrection.mYs[row]; 144 final float radius = touchPositionCorrection.mRadii[row]; 145 sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth; 146 sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight; 147 sweetSpotRadii[i] = radius * (float) Math.sqrt( 148 hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight); 149 } 150 } 151 i++; 152 } 153 } else { 154 sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null; 155 calculateSweetSpotParams = false; 156 } 157 158 mNativeProximityInfo = setProximityInfoNative("", MAX_PROXIMITY_CHARS_SIZE, 159 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, 160 proximityCharsArray, 161 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, 162 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); 163 } 164 165 public long getNativeProximityInfo() { 166 return mNativeProximityInfo; 167 } 168 169 @Override 170 protected void finalize() throws Throwable { 171 try { 172 if (mNativeProximityInfo != 0) { 173 releaseProximityInfoNative(mNativeProximityInfo); 174 mNativeProximityInfo = 0; 175 } 176 } finally { 177 super.finalize(); 178 } 179 } 180 181 private void computeNearestNeighbors(int defaultWidth, Set<Key> keys, 182 TouchPositionCorrection touchPositionCorrection, 183 Map<Integer, List<Integer>> additionalProximityChars) { 184 final Map<Integer, Key> keyCodeMap = new HashMap<Integer, Key>(); 185 for (final Key key : keys) { 186 keyCodeMap.put(key.mCode, key); 187 } 188 final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE); 189 final int threshold = thresholdBase * thresholdBase; 190 // Round-up so we don't have any pixels outside the grid 191 final Key[] neighborKeys = new Key[keys.size()]; 192 final int gridWidth = mGridWidth * mCellWidth; 193 final int gridHeight = mGridHeight * mCellHeight; 194 for (int x = 0; x < gridWidth; x += mCellWidth) { 195 for (int y = 0; y < gridHeight; y += mCellHeight) { 196 final int centerX = x + mCellWidth / 2; 197 final int centerY = y + mCellHeight / 2; 198 int count = 0; 199 for (final Key key : keys) { 200 if (key.isSpacer()) continue; 201 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) { 202 neighborKeys[count++] = key; 203 } 204 } 205 int currentCodesSize = count; 206 for (int i = 0; i < currentCodesSize; ++i) { 207 final int c = neighborKeys[i].mCode; 208 final List<Integer> additionalChars = additionalProximityChars.get(c); 209 if (additionalChars == null || additionalChars.size() == 0) { 210 continue; 211 } 212 for (int j = 0; j < additionalChars.size(); ++j) { 213 final int additionalChar = additionalChars.get(j); 214 boolean contains = false; 215 for (int k = 0; k < count; ++k) { 216 if(additionalChar == neighborKeys[k].mCode) { 217 contains = true; 218 break; 219 } 220 } 221 if (!contains) { 222 neighborKeys[count++] = keyCodeMap.get(additionalChar); 223 } 224 } 225 } 226 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = 227 Arrays.copyOfRange(neighborKeys, 0, count); 228 } 229 } 230 setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys, 231 touchPositionCorrection); 232 } 233 234 public Key[] getNearestKeys(int x, int y) { 235 if (mGridNeighbors == null) { 236 return EMPTY_KEY_ARRAY; 237 } 238 if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { 239 int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); 240 if (index < mGridSize) { 241 return mGridNeighbors[index]; 242 } 243 } 244 return EMPTY_KEY_ARRAY; 245 } 246} 247