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