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