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