• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.keyboard;
18 
19 import android.graphics.Rect;
20 import android.text.TextUtils;
21 import android.util.Log;
22 
23 import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
24 import com.android.inputmethod.latin.Constants;
25 import com.android.inputmethod.latin.JniUtils;
26 
27 import java.util.Arrays;
28 
29 public class ProximityInfo {
30     private static final String TAG = ProximityInfo.class.getSimpleName();
31     private static final boolean DEBUG = false;
32 
33     // Must be equal to MAX_PROXIMITY_CHARS_SIZE in native/jni/src/defines.h
34     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
35     /** Number of key widths from current touch point to search for nearest keys. */
36     private static final float SEARCH_DISTANCE = 1.2f;
37     private static final Key[] EMPTY_KEY_ARRAY = new Key[0];
38     private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f;
39 
40     private final int mGridWidth;
41     private final int mGridHeight;
42     private final int mGridSize;
43     private final int mCellWidth;
44     private final int mCellHeight;
45     // TODO: Find a proper name for mKeyboardMinWidth
46     private final int mKeyboardMinWidth;
47     private final int mKeyboardHeight;
48     private final int mMostCommonKeyWidth;
49     private final int mMostCommonKeyHeight;
50     private final Key[] mKeys;
51     private final Key[][] mGridNeighbors;
52     private final String mLocaleStr;
53 
ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight, final int minWidth, final int height, final int mostCommonKeyWidth, final int mostCommonKeyHeight, final Key[] keys, final TouchPositionCorrection touchPositionCorrection)54     ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
55             final int minWidth, final int height, final int mostCommonKeyWidth,
56             final int mostCommonKeyHeight, final Key[] keys,
57             final TouchPositionCorrection touchPositionCorrection) {
58         if (TextUtils.isEmpty(localeStr)) {
59             mLocaleStr = "";
60         } else {
61             mLocaleStr = localeStr;
62         }
63         mGridWidth = gridWidth;
64         mGridHeight = gridHeight;
65         mGridSize = mGridWidth * mGridHeight;
66         mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth;
67         mCellHeight = (height + mGridHeight - 1) / mGridHeight;
68         mKeyboardMinWidth = minWidth;
69         mKeyboardHeight = height;
70         mMostCommonKeyHeight = mostCommonKeyHeight;
71         mMostCommonKeyWidth = mostCommonKeyWidth;
72         mKeys = keys;
73         mGridNeighbors = new Key[mGridSize][];
74         if (minWidth == 0 || height == 0) {
75             // No proximity required. Keyboard might be more keys keyboard.
76             return;
77         }
78         computeNearestNeighbors();
79         mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
80     }
81 
82     private long mNativeProximityInfo;
83     static {
JniUtils.loadNativeLibrary()84         JniUtils.loadNativeLibrary();
85     }
86 
87     // TODO: Stop passing proximityCharsArray
setProximityInfoNative(String locale, int displayWidth, int displayHeight, int gridWidth, int gridHeight, int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray, int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths, int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii)88     private static native long setProximityInfoNative(String locale,
89             int displayWidth, int displayHeight, int gridWidth, int gridHeight,
90             int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray,
91             int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths,
92             int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs,
93             float[] sweetSpotCenterYs, float[] sweetSpotRadii);
94 
releaseProximityInfoNative(long nativeProximityInfo)95     private static native void releaseProximityInfoNative(long nativeProximityInfo);
96 
needsProximityInfo(final Key key)97     private static boolean needsProximityInfo(final Key key) {
98         // Don't include special keys into ProximityInfo.
99         return key.mCode >= Constants.CODE_SPACE;
100     }
101 
getProximityInfoKeysCount(final Key[] keys)102     private static int getProximityInfoKeysCount(final Key[] keys) {
103         int count = 0;
104         for (final Key key : keys) {
105             if (needsProximityInfo(key)) {
106                 count++;
107             }
108         }
109         return count;
110     }
111 
createNativeProximityInfo(final TouchPositionCorrection touchPositionCorrection)112     private long createNativeProximityInfo(final TouchPositionCorrection touchPositionCorrection) {
113         final Key[][] gridNeighborKeys = mGridNeighbors;
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             int infoIndex = i * MAX_PROXIMITY_CHARS_SIZE;
119             for (int j = 0; j < proximityCharsLength; ++j) {
120                 final Key neighborKey = gridNeighborKeys[i][j];
121                 // Excluding from proximityCharsArray
122                 if (!needsProximityInfo(neighborKey)) {
123                     continue;
124                 }
125                 proximityCharsArray[infoIndex] = neighborKey.mCode;
126                 infoIndex++;
127             }
128         }
129         if (DEBUG) {
130             final StringBuilder sb = new StringBuilder();
131             for (int i = 0; i < mGridSize; i++) {
132                 sb.setLength(0);
133                 for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; j++) {
134                     final int code = proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j];
135                     if (code == Constants.NOT_A_CODE) {
136                         break;
137                     }
138                     if (sb.length() > 0) sb.append(" ");
139                     sb.append(Constants.printableCode(code));
140                 }
141                 Log.d(TAG, "proxmityChars["+i+"]: " + sb);
142             }
143         }
144 
145         final Key[] keys = mKeys;
146         final int keyCount = getProximityInfoKeysCount(keys);
147         final int[] keyXCoordinates = new int[keyCount];
148         final int[] keyYCoordinates = new int[keyCount];
149         final int[] keyWidths = new int[keyCount];
150         final int[] keyHeights = new int[keyCount];
151         final int[] keyCharCodes = new int[keyCount];
152         final float[] sweetSpotCenterXs;
153         final float[] sweetSpotCenterYs;
154         final float[] sweetSpotRadii;
155 
156         for (int infoIndex = 0, keyIndex = 0; keyIndex < keys.length; keyIndex++) {
157             final Key key = keys[keyIndex];
158             // Excluding from key coordinate arrays
159             if (!needsProximityInfo(key)) {
160                 continue;
161             }
162             keyXCoordinates[infoIndex] = key.mX;
163             keyYCoordinates[infoIndex] = key.mY;
164             keyWidths[infoIndex] = key.mWidth;
165             keyHeights[infoIndex] = key.mHeight;
166             keyCharCodes[infoIndex] = key.mCode;
167             infoIndex++;
168         }
169 
170         if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
171             if (DEBUG) {
172                 Log.d(TAG, "touchPositionCorrection: ON");
173             }
174             sweetSpotCenterXs = new float[keyCount];
175             sweetSpotCenterYs = new float[keyCount];
176             sweetSpotRadii = new float[keyCount];
177             final int rows = touchPositionCorrection.getRows();
178             final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
179                     * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight);
180             for (int infoIndex = 0, keyIndex = 0; keyIndex < keys.length; keyIndex++) {
181                 final Key key = keys[keyIndex];
182                 // Excluding from touch position correction arrays
183                 if (!needsProximityInfo(key)) {
184                     continue;
185                 }
186                 final Rect hitBox = key.mHitBox;
187                 sweetSpotCenterXs[infoIndex] = hitBox.exactCenterX();
188                 sweetSpotCenterYs[infoIndex] = hitBox.exactCenterY();
189                 sweetSpotRadii[infoIndex] = defaultRadius;
190                 final int row = hitBox.top / mMostCommonKeyHeight;
191                 if (row < rows) {
192                     final int hitBoxWidth = hitBox.width();
193                     final int hitBoxHeight = hitBox.height();
194                     final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight);
195                     sweetSpotCenterXs[infoIndex] +=
196                             touchPositionCorrection.getX(row) * hitBoxWidth;
197                     sweetSpotCenterYs[infoIndex] +=
198                             touchPositionCorrection.getY(row) * hitBoxHeight;
199                     sweetSpotRadii[infoIndex] =
200                             touchPositionCorrection.getRadius(row) * hitBoxDiagonal;
201                 }
202                 if (DEBUG) {
203                     Log.d(TAG, String.format(
204                             "  [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", infoIndex, row,
205                             sweetSpotCenterXs[infoIndex], sweetSpotCenterYs[infoIndex],
206                             sweetSpotRadii[infoIndex], (row < rows ? "correct" : "default"),
207                             Constants.printableCode(key.mCode)));
208                 }
209                 infoIndex++;
210             }
211         } else {
212             sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
213             if (DEBUG) {
214                 Log.d(TAG, "touchPositionCorrection: OFF");
215             }
216         }
217 
218         // TODO: Stop passing proximityCharsArray
219         return setProximityInfoNative(mLocaleStr, mKeyboardMinWidth, mKeyboardHeight,
220                 mGridWidth, mGridHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
221                 proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths,
222                 keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
223     }
224 
getNativeProximityInfo()225     public long getNativeProximityInfo() {
226         return mNativeProximityInfo;
227     }
228 
229     @Override
finalize()230     protected void finalize() throws Throwable {
231         try {
232             if (mNativeProximityInfo != 0) {
233                 releaseProximityInfoNative(mNativeProximityInfo);
234                 mNativeProximityInfo = 0;
235             }
236         } finally {
237             super.finalize();
238         }
239     }
240 
computeNearestNeighbors()241     private void computeNearestNeighbors() {
242         final int defaultWidth = mMostCommonKeyWidth;
243         final Key[] keys = mKeys;
244         final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
245         final int threshold = thresholdBase * thresholdBase;
246         // Round-up so we don't have any pixels outside the grid
247         final Key[] neighborKeys = new Key[keys.length];
248         final int gridWidth = mGridWidth * mCellWidth;
249         final int gridHeight = mGridHeight * mCellHeight;
250         for (int x = 0; x < gridWidth; x += mCellWidth) {
251             for (int y = 0; y < gridHeight; y += mCellHeight) {
252                 final int centerX = x + mCellWidth / 2;
253                 final int centerY = y + mCellHeight / 2;
254                 int count = 0;
255                 for (final Key key : keys) {
256                     if (key.isSpacer()) continue;
257                     if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
258                         neighborKeys[count++] = key;
259                     }
260                 }
261                 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
262                         Arrays.copyOfRange(neighborKeys, 0, count);
263             }
264         }
265     }
266 
fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode, final int[] dest)267     public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
268             final int[] dest) {
269         final int destLength = dest.length;
270         if (destLength < 1) {
271             return;
272         }
273         int index = 0;
274         if (primaryKeyCode > Constants.CODE_SPACE) {
275             dest[index++] = primaryKeyCode;
276         }
277         final Key[] nearestKeys = getNearestKeys(x, y);
278         for (Key key : nearestKeys) {
279             if (index >= destLength) {
280                 break;
281             }
282             final int code = key.mCode;
283             if (code <= Constants.CODE_SPACE) {
284                 break;
285             }
286             dest[index++] = code;
287         }
288         if (index < destLength) {
289             dest[index] = Constants.NOT_A_CODE;
290         }
291     }
292 
getNearestKeys(final int x, final int y)293     public Key[] getNearestKeys(final int x, final int y) {
294         if (mGridNeighbors == null) {
295             return EMPTY_KEY_ARRAY;
296         }
297         if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
298             int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
299             if (index < mGridSize) {
300                 return mGridNeighbors[index];
301             }
302         }
303         return EMPTY_KEY_ARRAY;
304     }
305 }
306