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 21 import com.android.inputmethod.keyboard.internal.KeyboardParams.TouchPositionCorrection; 22 import com.android.inputmethod.latin.Utils; 23 import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; 24 25 import java.util.Arrays; 26 import java.util.Collections; 27 import java.util.List; 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 int[] EMPTY_INT_ARRAY = new int[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[][] mGridNeighbors; 45 46 ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth, 47 int keyHeight, List<Key> keys, TouchPositionCorrection touchPositionCorrection) { 48 mGridWidth = gridWidth; 49 mGridHeight = gridHeight; 50 mGridSize = mGridWidth * mGridHeight; 51 mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth; 52 mCellHeight = (height + mGridHeight - 1) / mGridHeight; 53 mKeyboardMinWidth = minWidth; 54 mKeyboardHeight = height; 55 mKeyHeight = keyHeight; 56 mGridNeighbors = new int[mGridSize][]; 57 if (minWidth == 0 || height == 0) { 58 // No proximity required. Keyboard might be mini keyboard. 59 return; 60 } 61 computeNearestNeighbors(keyWidth, keys, touchPositionCorrection); 62 } 63 64 public static ProximityInfo createDummyProximityInfo() { 65 return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptyList(), null); 66 } 67 68 public static ProximityInfo createSpellCheckerProximityInfo() { 69 final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); 70 spellCheckerProximityInfo.mNativeProximityInfo = 71 spellCheckerProximityInfo.setProximityInfoNative( 72 SpellCheckerProximityInfo.ROW_SIZE, 73 480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY, 74 0, null, null, null, null, null, null, null, null); 75 return spellCheckerProximityInfo; 76 } 77 78 private int mNativeProximityInfo; 79 static { 80 Utils.loadNativeLibrary(); 81 } 82 private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth, 83 int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray, 84 int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, 85 int[] keyWidths, int[] keyHeights, int[] keyCharCodes, 86 float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii); 87 private native void releaseProximityInfoNative(int nativeProximityInfo); 88 89 private final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth, 90 int keyboardHeight, List<Key> keys, 91 TouchPositionCorrection touchPositionCorrection) { 92 int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; 93 Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE); 94 for (int i = 0; i < mGridSize; ++i) { 95 final int proximityCharsLength = gridNeighborKeyIndexes[i].length; 96 for (int j = 0; j < proximityCharsLength; ++j) { 97 proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = 98 keys.get(gridNeighborKeyIndexes[i][j]).mCode; 99 } 100 } 101 final int keyCount = keys.size(); 102 final int[] keyXCoordinates = new int[keyCount]; 103 final int[] keyYCoordinates = new int[keyCount]; 104 final int[] keyWidths = new int[keyCount]; 105 final int[] keyHeights = new int[keyCount]; 106 final int[] keyCharCodes = new int[keyCount]; 107 for (int i = 0; i < keyCount; ++i) { 108 final Key key = keys.get(i); 109 keyXCoordinates[i] = key.mX; 110 keyYCoordinates[i] = key.mY; 111 keyWidths[i] = key.mWidth; 112 keyHeights[i] = key.mHeight; 113 keyCharCodes[i] = key.mCode; 114 } 115 116 float[] sweetSpotCenterXs = null; 117 float[] sweetSpotCenterYs = null; 118 float[] sweetSpotRadii = null; 119 120 if (touchPositionCorrection != null && touchPositionCorrection.isValid()) { 121 sweetSpotCenterXs = new float[keyCount]; 122 sweetSpotCenterYs = new float[keyCount]; 123 sweetSpotRadii = new float[keyCount]; 124 calculateSweetSpot(keys, touchPositionCorrection, 125 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); 126 } 127 128 mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE, 129 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray, 130 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, 131 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); 132 } 133 134 private void calculateSweetSpot(List<Key> keys, TouchPositionCorrection touchPositionCorrection, 135 float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii) { 136 final int keyCount = keys.size(); 137 final float[] xs = touchPositionCorrection.mXs; 138 final float[] ys = touchPositionCorrection.mYs; 139 final float[] radii = touchPositionCorrection.mRadii; 140 for (int i = 0; i < keyCount; ++i) { 141 final Key key = keys.get(i); 142 final Rect hitBox = key.mHitBox; 143 final int row = hitBox.top / mKeyHeight; 144 if (row < radii.length) { 145 final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f; 146 final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f; 147 final float hitBoxWidth = hitBox.right - hitBox.left; 148 final float hitBoxHeight = hitBox.bottom - hitBox.top; 149 final float x = xs[row]; 150 final float y = ys[row]; 151 final float radius = radii[row]; 152 sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth; 153 sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight; 154 sweetSpotRadii[i] = radius 155 * (float)Math.sqrt(hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight); 156 } 157 } 158 } 159 160 public int getNativeProximityInfo() { 161 return mNativeProximityInfo; 162 } 163 164 @Override 165 protected void finalize() throws Throwable { 166 try { 167 if (mNativeProximityInfo != 0) { 168 releaseProximityInfoNative(mNativeProximityInfo); 169 mNativeProximityInfo = 0; 170 } 171 } finally { 172 super.finalize(); 173 } 174 } 175 176 private void computeNearestNeighbors(int defaultWidth, List<Key> keys, 177 TouchPositionCorrection touchPositionCorrection) { 178 final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE); 179 final int threshold = thresholdBase * thresholdBase; 180 // Round-up so we don't have any pixels outside the grid 181 final int[] indices = new int[keys.size()]; 182 final int gridWidth = mGridWidth * mCellWidth; 183 final int gridHeight = mGridHeight * mCellHeight; 184 for (int x = 0; x < gridWidth; x += mCellWidth) { 185 for (int y = 0; y < gridHeight; y += mCellHeight) { 186 final int centerX = x + mCellWidth / 2; 187 final int centerY = y + mCellHeight / 2; 188 int count = 0; 189 for (int i = 0; i < keys.size(); i++) { 190 final Key key = keys.get(i); 191 if (key.isSpacer()) continue; 192 if (key.squaredDistanceToEdge(centerX, centerY) < threshold) 193 indices[count++] = i; 194 } 195 final int[] cell = new int[count]; 196 System.arraycopy(indices, 0, cell, 0, count); 197 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = cell; 198 } 199 } 200 setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys, 201 touchPositionCorrection); 202 } 203 204 public int[] getNearestKeys(int x, int y) { 205 if (mGridNeighbors == null) { 206 return EMPTY_INT_ARRAY; 207 } 208 if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { 209 int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); 210 if (index < mGridSize) { 211 return mGridNeighbors[index]; 212 } 213 } 214 return EMPTY_INT_ARRAY; 215 } 216 } 217