proximity_info_state.cpp revision 26c806620c26e048918624367ee624526613b0d2
1/*
2 * Copyright (C) 2012 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#define LOG_TAG "LatinIME: proximity_info_state.cpp"
18
19#include "suggest/core/layout/proximity_info_state.h"
20
21#include <algorithm>
22#include <cstring> // for memset() and memmove()
23#include <sstream> // for debug prints
24#include <vector>
25
26#include "defines.h"
27#include "suggest/core/layout/geometry_utils.h"
28#include "suggest/core/layout/proximity_info.h"
29#include "suggest/core/layout/proximity_info_state_utils.h"
30#include "utils/char_utils.h"
31
32namespace latinime {
33
34int ProximityInfoState::getPrimaryOriginalCodePointAt(const int index) const {
35    const int primaryCodePoint = getPrimaryCodePointAt(index);
36    const int keyIndex = mProximityInfo->getKeyIndexOf(primaryCodePoint);
37    return mProximityInfo->getOriginalCodePointOf(keyIndex);
38}
39
40// TODO: Remove the dependency of "isGeometric"
41void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
42        const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize,
43        const int *const xCoordinates, const int *const yCoordinates, const int *const times,
44        const int *const pointerIds, const bool isGeometric) {
45    ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH));
46    mIsContinuousSuggestionPossible = (mHasBeenUpdatedByGeometricInput != isGeometric) ?
47            false : ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
48                    inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
49                    &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
50    if (DEBUG_DICT) {
51        AKLOGI("isContinuousSuggestionPossible = %s",
52                (mIsContinuousSuggestionPossible ? "true" : "false"));
53    }
54
55    mProximityInfo = proximityInfo;
56    mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
57    mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
58    mKeyCount = proximityInfo->getKeyCount();
59    mCellHeight = proximityInfo->getCellHeight();
60    mCellWidth = proximityInfo->getCellWidth();
61    mGridHeight = proximityInfo->getGridWidth();
62    mGridWidth = proximityInfo->getGridHeight();
63
64    memset(mInputProximities, 0, sizeof(mInputProximities));
65
66    if (!isGeometric && pointerId == 0) {
67        mProximityInfo->initializeProximities(inputCodes, xCoordinates, yCoordinates,
68                inputSize, mInputProximities);
69    }
70
71    ///////////////////////
72    // Setup touch points
73    int pushTouchPointStartIndex = 0;
74    int lastSavedInputSize = 0;
75    mMaxPointToKeyLength = maxPointToKeyLength;
76    mSampledInputSize = 0;
77    mMostProbableStringProbability = 0.0f;
78
79    if (mIsContinuousSuggestionPossible && mSampledInputIndice.size() > 1) {
80        // Just update difference.
81        // Previous two points are never skipped. Thus, we pop 2 input point data here.
82        pushTouchPointStartIndex = ProximityInfoStateUtils::trimLastTwoTouchPoints(
83                &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache,
84                &mSampledInputIndice);
85        lastSavedInputSize = mSampledInputXs.size();
86    } else {
87        // Clear all data.
88        mSampledInputXs.clear();
89        mSampledInputYs.clear();
90        mSampledTimes.clear();
91        mSampledInputIndice.clear();
92        mSampledLengthCache.clear();
93        mSampledNormalizedSquaredLengthCache.clear();
94        mSampledNearKeySets.clear();
95        mSampledSearchKeySets.clear();
96        mSpeedRates.clear();
97        mBeelineSpeedPercentiles.clear();
98        mCharProbabilities.clear();
99        mDirections.clear();
100    }
101
102    if (DEBUG_GEO_FULL) {
103        AKLOGI("Init ProximityInfoState: reused points =  %d, last input size = %d",
104                pushTouchPointStartIndex, lastSavedInputSize);
105    }
106
107    if (xCoordinates && yCoordinates) {
108        mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(mProximityInfo,
109                mMaxPointToKeyLength, mInputProximities, xCoordinates, yCoordinates, times,
110                pointerIds, inputSize, isGeometric, pointerId,
111                pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes,
112                &mSampledLengthCache, &mSampledInputIndice);
113    }
114
115    if (mSampledInputSize > 0 && isGeometric) {
116        mAverageSpeed = ProximityInfoStateUtils::refreshSpeedRates(inputSize, xCoordinates,
117                yCoordinates, times, lastSavedInputSize, mSampledInputSize, &mSampledInputXs,
118                &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, &mSampledInputIndice,
119                &mSpeedRates, &mDirections);
120        ProximityInfoStateUtils::refreshBeelineSpeedRates(mProximityInfo->getMostCommonKeyWidth(),
121                mAverageSpeed, inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
122                &mSampledInputXs, &mSampledInputYs, &mSampledInputIndice,
123                &mBeelineSpeedPercentiles);
124    }
125
126    if (mSampledInputSize > 0) {
127        ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize,
128                lastSavedInputSize, isGeometric, &mSampledInputXs, &mSampledInputYs,
129                &mSampledNearKeySets, &mSampledNormalizedSquaredLengthCache);
130        if (isGeometric) {
131            // updates probabilities of skipping or mapping each key for all points.
132            ProximityInfoStateUtils::updateAlignPointProbabilities(
133                    mMaxPointToKeyLength, mProximityInfo->getMostCommonKeyWidth(),
134                    mProximityInfo->getKeyCount(), lastSavedInputSize, mSampledInputSize,
135                    &mSampledInputXs, &mSampledInputYs, &mSpeedRates, &mSampledLengthCache,
136                    &mSampledNormalizedSquaredLengthCache, &mSampledNearKeySets,
137                    mProximityInfo, &mCharProbabilities);
138            ProximityInfoStateUtils::updateSampledSearchKeySets(mProximityInfo,
139                    mSampledInputSize, lastSavedInputSize, &mSampledLengthCache,
140                    &mSampledNearKeySets, &mSampledSearchKeySets,
141                    &mSampledSearchKeyVectors);
142            mMostProbableStringProbability = ProximityInfoStateUtils::getMostProbableString(
143                    mProximityInfo, mSampledInputSize, &mCharProbabilities, mMostProbableString);
144
145        }
146    }
147
148    if (DEBUG_SAMPLING_POINTS) {
149        ProximityInfoStateUtils::dump(isGeometric, inputSize, xCoordinates, yCoordinates,
150                mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSpeedRates,
151                &mBeelineSpeedPercentiles);
152    }
153    // end
154    ///////////////////////
155
156    mTouchPositionCorrectionEnabled = mSampledInputSize > 0 && mHasTouchPositionCorrectionData
157            && xCoordinates && yCoordinates;
158    if (!isGeometric && pointerId == 0) {
159        ProximityInfoStateUtils::initPrimaryInputWord(
160                inputSize, mInputProximities, mPrimaryInputWord);
161    }
162    if (DEBUG_GEO_FULL) {
163        AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize);
164    }
165    mHasBeenUpdatedByGeometricInput = isGeometric;
166}
167
168// This function basically converts from a length to an edit distance. Accordingly, it's obviously
169// wrong to compare with mMaxPointToKeyLength.
170float ProximityInfoState::getPointToKeyLength(
171        const int inputIndex, const int codePoint) const {
172    const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
173    if (keyId != NOT_AN_INDEX) {
174        const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
175        return std::min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
176    }
177    if (CharUtils::isIntentionalOmissionCodePoint(codePoint)) {
178        return 0.0f;
179    }
180    // If the char is not a key on the keyboard then return the max length.
181    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
182}
183
184float ProximityInfoState::getPointToKeyByIdLength(
185        const int inputIndex, const int keyId) const {
186    return ProximityInfoStateUtils::getPointToKeyByIdLength(mMaxPointToKeyLength,
187            &mSampledNormalizedSquaredLengthCache, mProximityInfo->getKeyCount(), inputIndex,
188            keyId);
189}
190
191// In the following function, c is the current character of the dictionary word currently examined.
192// currentChars is an array containing the keys close to the character the user actually typed at
193// the same position. We want to see if c is in it: if so, then the word contains at that position
194// a character close to what the user typed.
195// What the user typed is actually the first character of the array.
196// proximityIndex is a pointer to the variable where getProximityType returns the index of c
197// in the proximity chars of the input index.
198// Notice : accented characters do not have a proximity list, so they are alone in their list. The
199// non-accented version of the character should be considered "close", but not the other keys close
200// to the non-accented version.
201ProximityType ProximityInfoState::getProximityType(const int index, const int codePoint,
202        const bool checkProximityChars, int *proximityIndex) const {
203    const int *currentCodePoints = getProximityCodePointsAt(index);
204    const int firstCodePoint = currentCodePoints[0];
205    const int baseLowerC = CharUtils::toBaseLowerCase(codePoint);
206
207    // The first char in the array is what user typed. If it matches right away, that means the
208    // user typed that same char for this pos.
209    if (firstCodePoint == baseLowerC || firstCodePoint == codePoint) {
210        return MATCH_CHAR;
211    }
212
213    if (!checkProximityChars) return SUBSTITUTION_CHAR;
214
215    // If the non-accented, lowercased version of that first character matches c, then we have a
216    // non-accented version of the accented character the user typed. Treat it as a close char.
217    if (CharUtils::toBaseLowerCase(firstCodePoint) == baseLowerC) {
218        return PROXIMITY_CHAR;
219    }
220
221    // Not an exact nor an accent-alike match: search the list of close keys
222    int j = 1;
223    while (j < MAX_PROXIMITY_CHARS_SIZE
224            && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
225        const bool matched = (currentCodePoints[j] == baseLowerC
226                || currentCodePoints[j] == codePoint);
227        if (matched) {
228            if (proximityIndex) {
229                *proximityIndex = j;
230            }
231            return PROXIMITY_CHAR;
232        }
233        ++j;
234    }
235    if (j < MAX_PROXIMITY_CHARS_SIZE
236            && currentCodePoints[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
237        ++j;
238        while (j < MAX_PROXIMITY_CHARS_SIZE
239                && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
240            const bool matched = (currentCodePoints[j] == baseLowerC
241                    || currentCodePoints[j] == codePoint);
242            if (matched) {
243                if (proximityIndex) {
244                    *proximityIndex = j;
245                }
246                return ADDITIONAL_PROXIMITY_CHAR;
247            }
248            ++j;
249        }
250    }
251    // Was not included, signal this as a substitution character.
252    return SUBSTITUTION_CHAR;
253}
254
255ProximityType ProximityInfoState::getProximityTypeG(const int index, const int codePoint) const {
256    if (!isUsed()) {
257        return UNRELATED_CHAR;
258    }
259    const int sampledSearchKeyVectorsSize = static_cast<int>(mSampledSearchKeyVectors.size());
260    if (index < 0 || index >= sampledSearchKeyVectorsSize) {
261        AKLOGE("getProximityTypeG() is called with an invalid index(%d). "
262                "mSampledSearchKeyVectors.size() = %d, codePoint = %x.", index,
263                sampledSearchKeyVectorsSize, codePoint);
264        ASSERT(false);
265        return UNRELATED_CHAR;
266    }
267    const int lowerCodePoint = CharUtils::toLowerCase(codePoint);
268    const int baseLowerCodePoint = CharUtils::toBaseCodePoint(lowerCodePoint);
269    for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) {
270        if (mSampledSearchKeyVectors[index][i] == lowerCodePoint
271                || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) {
272            return MATCH_CHAR;
273        }
274    }
275    return UNRELATED_CHAR;
276}
277
278bool ProximityInfoState::isKeyInSerchKeysAfterIndex(const int index, const int keyId) const {
279    ASSERT(keyId >= 0 && index >= 0 && index < mSampledInputSize);
280    return mSampledSearchKeySets[index].test(keyId);
281}
282
283float ProximityInfoState::getDirection(const int index0, const int index1) const {
284    return ProximityInfoStateUtils::getDirection(
285            &mSampledInputXs, &mSampledInputYs, index0, index1);
286}
287
288float ProximityInfoState::getMostProbableString(int *const codePointBuf) const {
289    memmove(codePointBuf, mMostProbableString, sizeof(mMostProbableString));
290    return mMostProbableStringProbability;
291}
292
293bool ProximityInfoState::hasSpaceProximity(const int index) const {
294    ASSERT(0 <= index && index < mSampledInputSize);
295    return mProximityInfo->hasSpaceProximity(getInputX(index), getInputY(index));
296}
297
298// Returns a probability of mapping index to keyIndex.
299float ProximityInfoState::getProbability(const int index, const int keyIndex) const {
300    ASSERT(0 <= index && index < mSampledInputSize);
301    hash_map_compat<int, float>::const_iterator it = mCharProbabilities[index].find(keyIndex);
302    if (it != mCharProbabilities[index].end()) {
303        return it->second;
304    }
305    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
306}
307} // namespace latinime
308