| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package com.android.inputmethod.keyboard; |
| |
| import android.graphics.Rect; |
| |
| import com.android.inputmethod.keyboard.Key; |
| import com.android.inputmethod.latin.Utils; |
| import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public class ProximityInfo { |
| public static final int MAX_PROXIMITY_CHARS_SIZE = 16; |
| /** Number of key widths from current touch point to search for nearest keys. */ |
| private static float SEARCH_DISTANCE = 1.2f; |
| private static final int[] EMPTY_INT_ARRAY = new int[0]; |
| |
| private final int mKeyHeight; |
| private final int mGridWidth; |
| private final int mGridHeight; |
| private final int mGridSize; |
| private final int mCellWidth; |
| private final int mCellHeight; |
| // TODO: Find a proper name for mKeyboardMinWidth |
| private final int mKeyboardMinWidth; |
| private final int mKeyboardHeight; |
| private final int[][] mGridNeighbors; |
| |
| private final float[] mTouchPositionCorrectionXs; |
| private final float[] mTouchPositionCorrectionYs; |
| private final float[] mTouchPositionCorrectionRadii; |
| |
| ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth, |
| int keyHeight, List<Key> keys, float[] touchPositionCorrectionXs, |
| float[] touchPositionCorrectionYs, float[] touchPositionCorrectionRadii) { |
| mGridWidth = gridWidth; |
| mGridHeight = gridHeight; |
| mGridSize = mGridWidth * mGridHeight; |
| mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth; |
| mCellHeight = (height + mGridHeight - 1) / mGridHeight; |
| mKeyboardMinWidth = minWidth; |
| mKeyboardHeight = height; |
| mKeyHeight = keyHeight; |
| mTouchPositionCorrectionXs = touchPositionCorrectionXs; |
| mTouchPositionCorrectionYs = touchPositionCorrectionYs; |
| mTouchPositionCorrectionRadii = touchPositionCorrectionRadii; |
| mGridNeighbors = new int[mGridSize][]; |
| if (minWidth == 0 || height == 0) { |
| // No proximity required. Keyboard might be mini keyboard. |
| return; |
| } |
| computeNearestNeighbors(keyWidth, keys); |
| } |
| |
| public static ProximityInfo createDummyProximityInfo() { |
| return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptyList(), null, null, null); |
| } |
| |
| public static ProximityInfo createSpellCheckerProximityInfo() { |
| final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); |
| spellCheckerProximityInfo.mNativeProximityInfo = |
| spellCheckerProximityInfo.setProximityInfoNative( |
| SpellCheckerProximityInfo.ROW_SIZE, |
| 480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY, |
| 0, null, null, null, null, null, null, null, null); |
| return spellCheckerProximityInfo; |
| } |
| |
| private int mNativeProximityInfo; |
| static { |
| Utils.loadNativeLibrary(); |
| } |
| private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth, |
| int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray, |
| int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, |
| int[] keyWidths, int[] keyHeights, int[] keyCharCodes, |
| float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii); |
| private native void releaseProximityInfoNative(int nativeProximityInfo); |
| |
| private final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth, |
| int keyboardHeight, List<Key> keys) { |
| int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; |
| Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE); |
| for (int i = 0; i < mGridSize; ++i) { |
| final int proximityCharsLength = gridNeighborKeyIndexes[i].length; |
| for (int j = 0; j < proximityCharsLength; ++j) { |
| proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = |
| keys.get(gridNeighborKeyIndexes[i][j]).mCode; |
| } |
| } |
| final int keyCount = keys.size(); |
| final int[] keyXCoordinates = new int[keyCount]; |
| final int[] keyYCoordinates = new int[keyCount]; |
| final int[] keyWidths = new int[keyCount]; |
| final int[] keyHeights = new int[keyCount]; |
| final int[] keyCharCodes = new int[keyCount]; |
| for (int i = 0; i < keyCount; ++i) { |
| final Key key = keys.get(i); |
| keyXCoordinates[i] = key.mX; |
| keyYCoordinates[i] = key.mY; |
| keyWidths[i] = key.mWidth; |
| keyHeights[i] = key.mHeight; |
| keyCharCodes[i] = key.mCode; |
| } |
| |
| final boolean hasTouchPositionCorrectionData = |
| mTouchPositionCorrectionXs != null |
| && mTouchPositionCorrectionYs != null |
| && mTouchPositionCorrectionRadii != null |
| && mTouchPositionCorrectionXs.length > 0 |
| && mTouchPositionCorrectionYs.length > 0 |
| && mTouchPositionCorrectionRadii.length > 0; |
| final float[] sweetSpotCenterXs = |
| hasTouchPositionCorrectionData ? new float[keyCount] : null; |
| final float[] sweetSpotCenterYs = |
| hasTouchPositionCorrectionData ? new float[keyCount] : null; |
| final float[] sweetSpotRadii = |
| hasTouchPositionCorrectionData ? new float[keyCount] : null; |
| if (hasTouchPositionCorrectionData) { |
| calculateSweetSpot(keys, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); |
| } |
| |
| mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE, |
| keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray, |
| keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, |
| sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); |
| } |
| |
| private void calculateSweetSpot(List<Key> keys, float[] sweetSpotCenterXs, |
| float[] sweetSpotCenterYs, float[] sweetSpotRadii) { |
| final int keyCount = keys.size(); |
| for (int i = 0; i < keyCount; ++i) { |
| final Key key = keys.get(i); |
| final Rect hitBox = key.mHitBox; |
| final int row = hitBox.top / mKeyHeight; |
| if (row < mTouchPositionCorrectionRadii.length) { |
| final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f; |
| final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f; |
| final float hitBoxWidth = hitBox.right - hitBox.left; |
| final float hitBoxHeight = hitBox.bottom - hitBox.top; |
| final float x = mTouchPositionCorrectionXs[row]; |
| final float y = mTouchPositionCorrectionYs[row]; |
| final float radius = mTouchPositionCorrectionRadii[row]; |
| sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth; |
| sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight; |
| sweetSpotRadii[i] = radius |
| * (float)Math.sqrt(hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight); |
| } |
| } |
| } |
| |
| public int getNativeProximityInfo() { |
| return mNativeProximityInfo; |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| if (mNativeProximityInfo != 0) { |
| releaseProximityInfoNative(mNativeProximityInfo); |
| mNativeProximityInfo = 0; |
| } |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| private void computeNearestNeighbors(int defaultWidth, List<Key> keys) { |
| final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE); |
| final int threshold = thresholdBase * thresholdBase; |
| // Round-up so we don't have any pixels outside the grid |
| final int[] indices = new int[keys.size()]; |
| final int gridWidth = mGridWidth * mCellWidth; |
| final int gridHeight = mGridHeight * mCellHeight; |
| for (int x = 0; x < gridWidth; x += mCellWidth) { |
| for (int y = 0; y < gridHeight; y += mCellHeight) { |
| final int centerX = x + mCellWidth / 2; |
| final int centerY = y + mCellHeight / 2; |
| int count = 0; |
| for (int i = 0; i < keys.size(); i++) { |
| final Key key = keys.get(i); |
| if (key.isSpacer()) continue; |
| if (key.squaredDistanceToEdge(centerX, centerY) < threshold) |
| indices[count++] = i; |
| } |
| final int[] cell = new int[count]; |
| System.arraycopy(indices, 0, cell, 0, count); |
| mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = cell; |
| } |
| } |
| setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys); |
| } |
| |
| public int[] getNearestKeys(int x, int y) { |
| if (mGridNeighbors == null) { |
| return EMPTY_INT_ARRAY; |
| } |
| if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { |
| int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); |
| if (index < mGridSize) { |
| return mGridNeighbors[index]; |
| } |
| } |
| return EMPTY_INT_ARRAY; |
| } |
| } |