ProximityInfo.java revision 240871ecafde7834ebb4270cd7758fc904a5f3a7
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;
21import android.util.Log;
22
23import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
24import com.android.inputmethod.latin.Constants;
25import com.android.inputmethod.latin.JniUtils;
26
27import java.util.Arrays;
28
29public final class ProximityInfo {
30    private static final String TAG = ProximityInfo.class.getSimpleName();
31    private static final boolean DEBUG = false;
32
33    /** MAX_PROXIMITY_CHARS_SIZE must be the same as MAX_PROXIMITY_CHARS_SIZE_INTERNAL
34     * in defines.h */
35    public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
36    /** Number of key widths from current touch point to search for nearest keys. */
37    private static final float SEARCH_DISTANCE = 1.2f;
38    private static final Key[] EMPTY_KEY_ARRAY = new Key[0];
39    private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f;
40
41    private final int mGridWidth;
42    private final int mGridHeight;
43    private final int mGridSize;
44    private final int mCellWidth;
45    private final int mCellHeight;
46    // TODO: Find a proper name for mKeyboardMinWidth
47    private final int mKeyboardMinWidth;
48    private final int mKeyboardHeight;
49    private final int mMostCommonKeyWidth;
50    private final int mMostCommonKeyHeight;
51    private final Key[] mKeys;
52    private final Key[][] mGridNeighbors;
53    private final String mLocaleStr;
54
55    ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
56            final int minWidth, final int height, final int mostCommonKeyWidth,
57            final int mostCommonKeyHeight, final Key[] keys,
58            final TouchPositionCorrection touchPositionCorrection) {
59        if (TextUtils.isEmpty(localeStr)) {
60            mLocaleStr = "";
61        } else {
62            mLocaleStr = localeStr;
63        }
64        mGridWidth = gridWidth;
65        mGridHeight = gridHeight;
66        mGridSize = mGridWidth * mGridHeight;
67        mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth;
68        mCellHeight = (height + mGridHeight - 1) / mGridHeight;
69        mKeyboardMinWidth = minWidth;
70        mKeyboardHeight = height;
71        mMostCommonKeyHeight = mostCommonKeyHeight;
72        mMostCommonKeyWidth = mostCommonKeyWidth;
73        mKeys = keys;
74        mGridNeighbors = new Key[mGridSize][];
75        if (minWidth == 0 || height == 0) {
76            // No proximity required. Keyboard might be more keys keyboard.
77            return;
78        }
79        computeNearestNeighbors();
80        mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
81    }
82
83    public static ProximityInfo createDummyProximityInfo() {
84        return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
85    }
86
87    public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
88            final int rowSize, final int gridWidth, final int gridHeight) {
89        final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
90        spellCheckerProximityInfo.mNativeProximityInfo =
91                spellCheckerProximityInfo.setProximityInfoNative("",
92                        rowSize, gridWidth, gridHeight, gridWidth, gridHeight,
93                        1, proximity, 0, null, null, null, null, null, null, null, null);
94        return spellCheckerProximityInfo;
95    }
96
97    private long mNativeProximityInfo;
98    static {
99        JniUtils.loadNativeLibrary();
100    }
101
102    private native long setProximityInfoNative(
103            String locale, int maxProximityCharsSize, int displayWidth,
104            int displayHeight, int gridWidth, int gridHeight,
105            int mostCommonKeyWidth, int[] proximityCharsArray,
106            int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
107            int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
108            float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
109
110    private native void releaseProximityInfoNative(long nativeProximityInfo);
111
112    private final long createNativeProximityInfo(
113            final TouchPositionCorrection touchPositionCorrection) {
114        final Key[][] gridNeighborKeys = mGridNeighbors;
115        final int keyboardWidth = mKeyboardMinWidth;
116        final int keyboardHeight = mKeyboardHeight;
117        final Key[] keys = mKeys;
118        final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
119        Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
120        for (int i = 0; i < mGridSize; ++i) {
121            final int proximityCharsLength = gridNeighborKeys[i].length;
122            for (int j = 0; j < proximityCharsLength; ++j) {
123                proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] =
124                        gridNeighborKeys[i][j].mCode;
125            }
126        }
127        final int keyCount = keys.length;
128        final int[] keyXCoordinates = new int[keyCount];
129        final int[] keyYCoordinates = new int[keyCount];
130        final int[] keyWidths = new int[keyCount];
131        final int[] keyHeights = new int[keyCount];
132        final int[] keyCharCodes = new int[keyCount];
133        final float[] sweetSpotCenterXs;
134        final float[] sweetSpotCenterYs;
135        final float[] sweetSpotRadii;
136
137        for (int i = 0; i < keyCount; ++i) {
138            final Key key = keys[i];
139            keyXCoordinates[i] = key.mX;
140            keyYCoordinates[i] = key.mY;
141            keyWidths[i] = key.mWidth;
142            keyHeights[i] = key.mHeight;
143            keyCharCodes[i] = key.mCode;
144        }
145
146        if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
147            if (DEBUG) {
148                Log.d(TAG, "touchPositionCorrection: ON");
149            }
150            sweetSpotCenterXs = new float[keyCount];
151            sweetSpotCenterYs = new float[keyCount];
152            sweetSpotRadii = new float[keyCount];
153            final int rows = touchPositionCorrection.getRows();
154            final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
155                    * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight);
156            for (int i = 0; i < keyCount; i++) {
157                final Key key = keys[i];
158                final Rect hitBox = key.mHitBox;
159                sweetSpotCenterXs[i] = hitBox.exactCenterX();
160                sweetSpotCenterYs[i] = hitBox.exactCenterY();
161                sweetSpotRadii[i] = defaultRadius;
162                final int row = hitBox.top / mMostCommonKeyHeight;
163                if (row < rows) {
164                    final int hitBoxWidth = hitBox.width();
165                    final int hitBoxHeight = hitBox.height();
166                    final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight);
167                    sweetSpotCenterXs[i] += touchPositionCorrection.getX(row) * hitBoxWidth;
168                    sweetSpotCenterYs[i] += touchPositionCorrection.getY(row) * hitBoxHeight;
169                    sweetSpotRadii[i] = touchPositionCorrection.getRadius(row) * hitBoxDiagonal;
170                }
171                if (DEBUG) {
172                    Log.d(TAG, String.format(
173                            "  [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", i, row,
174                            sweetSpotCenterXs[i], sweetSpotCenterYs[i], sweetSpotRadii[i],
175                            (row < rows ? "correct" : "default"),
176                            Constants.printableCode(key.mCode)));
177                }
178            }
179        } else {
180            sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
181            if (DEBUG) {
182                Log.d(TAG, "touchPositionCorrection: OFF");
183            }
184        }
185
186        return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
187                keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth,
188                proximityCharsArray,
189                keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
190                sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
191    }
192
193    public long getNativeProximityInfo() {
194        return mNativeProximityInfo;
195    }
196
197    @Override
198    protected void finalize() throws Throwable {
199        try {
200            if (mNativeProximityInfo != 0) {
201                releaseProximityInfoNative(mNativeProximityInfo);
202                mNativeProximityInfo = 0;
203            }
204        } finally {
205            super.finalize();
206        }
207    }
208
209    private void computeNearestNeighbors() {
210        final int defaultWidth = mMostCommonKeyWidth;
211        final Key[] keys = mKeys;
212        final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
213        final int threshold = thresholdBase * thresholdBase;
214        // Round-up so we don't have any pixels outside the grid
215        final Key[] neighborKeys = new Key[keys.length];
216        final int gridWidth = mGridWidth * mCellWidth;
217        final int gridHeight = mGridHeight * mCellHeight;
218        for (int x = 0; x < gridWidth; x += mCellWidth) {
219            for (int y = 0; y < gridHeight; y += mCellHeight) {
220                final int centerX = x + mCellWidth / 2;
221                final int centerY = y + mCellHeight / 2;
222                int count = 0;
223                for (final Key key : keys) {
224                    if (key.isSpacer()) continue;
225                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
226                        neighborKeys[count++] = key;
227                    }
228                }
229                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
230                        Arrays.copyOfRange(neighborKeys, 0, count);
231            }
232        }
233    }
234
235    public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
236            final int[] dest) {
237        final int destLength = dest.length;
238        if (destLength < 1) {
239            return;
240        }
241        int index = 0;
242        if (primaryKeyCode > Constants.CODE_SPACE) {
243            dest[index++] = primaryKeyCode;
244        }
245        final Key[] nearestKeys = getNearestKeys(x, y);
246        for (Key key : nearestKeys) {
247            if (index >= destLength) {
248                break;
249            }
250            final int code = key.mCode;
251            if (code <= Constants.CODE_SPACE) {
252                break;
253            }
254            dest[index++] = code;
255        }
256        if (index < destLength) {
257            dest[index] = Constants.NOT_A_CODE;
258        }
259    }
260
261    public Key[] getNearestKeys(final int x, final int y) {
262        if (mGridNeighbors == null) {
263            return EMPTY_KEY_ARRAY;
264        }
265        if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
266            int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
267            if (index < mGridSize) {
268                return mGridNeighbors[index];
269            }
270        }
271        return EMPTY_KEY_ARRAY;
272    }
273}
274