17a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard/* 27a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * Copyright (C) 2014 The Android Open Source Project 37a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * 47a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * Licensed under the Apache License, Version 2.0 (the "License"); 57a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * you may not use this file except in compliance with the License. 67a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * You may obtain a copy of the License at 77a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * 87a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * http://www.apache.org/licenses/LICENSE-2.0 97a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * 107a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * Unless required by applicable law or agreed to in writing, software 117a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * distributed under the License is distributed on an "AS IS" BASIS, 127a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * See the License for the specific language governing permissions and 147a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * limitations under the License. 157a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard */ 167a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 177a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardpackage com.android.inputmethod.latin.spellcheck; 187a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 195f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaokaimport android.annotation.TargetApi; 207a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport android.content.res.Resources; 215f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaokaimport android.os.Build; 227a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport android.view.textservice.SentenceSuggestionsInfo; 237a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport android.view.textservice.SuggestionsInfo; 247a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport android.view.textservice.TextInfo; 257a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 2686f36003fd4397143bd37938dda029e5707634afYohei Yukawaimport com.android.inputmethod.compat.TextInfoCompatUtils; 279342484e8d573a40f470b6a593df31c602fa4076Ken Wakasaimport com.android.inputmethod.latin.common.Constants; 287a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport com.android.inputmethod.latin.settings.SpacingAndPunctuations; 297a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport com.android.inputmethod.latin.utils.RunInLocale; 307a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 317a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport java.util.ArrayList; 327a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardimport java.util.Locale; 337a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 347a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard/** 357a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in 367a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * the framework; maybe that should be protected instead, so that implementers don't have to 377a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * rewrite everything for any small change. 387a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard */ 397a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalardpublic class SentenceLevelAdapter { 40da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard private static class EmptySentenceSuggestionsInfosInitializationHolder { 41da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS = 42da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard new SentenceSuggestionsInfo[]{}; 43da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard } 447a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null); 45da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard 46da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard public static SentenceSuggestionsInfo[] getEmptySentenceSuggestionsInfo() { 47da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard return EmptySentenceSuggestionsInfosInitializationHolder.EMPTY_SENTENCE_SUGGESTIONS_INFOS; 48da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard } 49da70b90aa77f2caf88ebd0d102c7a28e0f9726adJean Chalard 507a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard /** 517a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * Container for split TextInfo parameters 527a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard */ 537a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public static class SentenceWordItem { 547a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public final TextInfo mTextInfo; 557a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public final int mStart; 567a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public final int mLength; 577a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public SentenceWordItem(TextInfo ti, int start, int end) { 587a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mTextInfo = ti; 597a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mStart = start; 607a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mLength = end - start; 617a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 627a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 637a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 647a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard /** 657a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard * Container for originally queried TextInfo and parameters 667a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard */ 677a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public static class SentenceTextInfoParams { 687a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final TextInfo mOriginalTextInfo; 697a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final ArrayList<SentenceWordItem> mItems; 707a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int mSize; 717a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) { 727a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mOriginalTextInfo = ti; 737a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mItems = items; 747a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mSize = items.size(); 757a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 767a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 777a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 787a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard private static class WordIterator { 797a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard private final SpacingAndPunctuations mSpacingAndPunctuations; 807a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public WordIterator(final Resources res, final Locale locale) { 815f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka final RunInLocale<SpacingAndPunctuations> job = 825f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka new RunInLocale<SpacingAndPunctuations>() { 837a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard @Override 845f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka protected SpacingAndPunctuations job(final Resources r) { 855f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka return new SpacingAndPunctuations(r); 867a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 877a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard }; 887a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mSpacingAndPunctuations = job.runInLocale(res, locale); 897a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 907a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 915f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka public int getEndOfWord(final CharSequence sequence, final int fromIndex) { 927a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int length = sequence.length(); 935f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1); 947a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard while (index < length) { 957a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int codePoint = Character.codePointAt(sequence, index); 967a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (mSpacingAndPunctuations.isWordSeparator(codePoint)) { 977a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard // If it's a period, we want to stop here only if it's followed by another 987a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard // word separator. In all other cases we stop here. 997a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (Constants.CODE_PERIOD == codePoint) { 1007a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int indexOfNextCodePoint = 1017a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard index + Character.charCount(Constants.CODE_PERIOD); 1027a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (indexOfNextCodePoint < length 1037a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard && mSpacingAndPunctuations.isWordSeparator( 1047a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard Character.codePointAt(sequence, indexOfNextCodePoint))) { 1057a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return index; 1067a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1077a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } else { 1087a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return index; 1097a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1107a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1117a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard index += Character.charCount(codePoint); 1127a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1137a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return index; 1147a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1157a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 1165f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka public int getBeginningOfNextWord(final CharSequence sequence, final int fromIndex) { 1177a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int length = sequence.length(); 1185f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka if (fromIndex >= length) { 1197a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return -1; 1207a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1215f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1); 1227a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard while (index < length) { 1237a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int codePoint = Character.codePointAt(sequence, index); 1247a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) { 1257a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return index; 1267a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1277a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard index += Character.charCount(codePoint); 1287a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1297a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return -1; 1307a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1317a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1327a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 1337a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard private final WordIterator mWordIterator; 1347a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public SentenceLevelAdapter(final Resources res, final Locale locale) { 1357a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard mWordIterator = new WordIterator(res, locale); 1367a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1377a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 1387a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) { 1397a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final WordIterator wordIterator = mWordIterator; 14086f36003fd4397143bd37938dda029e5707634afYohei Yukawa final CharSequence originalText = 14186f36003fd4397143bd37938dda029e5707634afYohei Yukawa TextInfoCompatUtils.getCharSequenceOrString(originalTextInfo); 1427a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int cookie = originalTextInfo.getCookie(); 1437a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int start = -1; 1447a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int end = originalText.length(); 1455f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka final ArrayList<SentenceWordItem> wordItems = new ArrayList<>(); 1467a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard int wordStart = wordIterator.getBeginningOfNextWord(originalText, start); 1477a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard int wordEnd = wordIterator.getEndOfWord(originalText, wordStart); 1487a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard while (wordStart <= end && wordEnd != -1 && wordStart != -1) { 1497a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (wordEnd >= start && wordEnd > wordStart) { 1503852cfa717a80e617e7748865ed7465931c54a12Yohei Yukawa final TextInfo ti = TextInfoCompatUtils.newInstance(originalText, wordStart, 1513852cfa717a80e617e7748865ed7465931c54a12Yohei Yukawa wordEnd, cookie, originalText.subSequence(wordStart, wordEnd).hashCode()); 1527a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd)); 1537a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1547a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd); 1557a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (wordStart == -1) { 1567a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard break; 1577a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1587a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard wordEnd = wordIterator.getEndOfWord(originalText, wordStart); 1597a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1607a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return new SentenceTextInfoParams(originalTextInfo, wordItems); 1617a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1627a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 1635f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 1647a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard public static SentenceSuggestionsInfo reconstructSuggestions( 1657a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) { 1667a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (results == null || results.length == 0) { 1677a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return null; 1687a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1697a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (originalTextInfoParams == null) { 1707a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return null; 1717a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1727a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie(); 1737a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int originalSequence = 1747a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard originalTextInfoParams.mOriginalTextInfo.getSequence(); 1757a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard 1767a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int querySize = originalTextInfoParams.mSize; 1777a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int[] offsets = new int[querySize]; 1787a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final int[] lengths = new int[querySize]; 1797a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize]; 1807a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard for (int i = 0; i < querySize; ++i) { 1817a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final SentenceWordItem item = originalTextInfoParams.mItems.get(i); 1827a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard SuggestionsInfo result = null; 1837a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard for (int j = 0; j < results.length; ++j) { 1847a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard final SuggestionsInfo cur = results[j]; 1857a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) { 1867a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard result = cur; 1877a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard result.setCookieAndSequence(originalCookie, originalSequence); 1887a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard break; 1897a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1907a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1917a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard offsets[i] = item.mStart; 1927a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard lengths[i] = item.mLength; 1937a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO; 1947a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1957a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths); 1967a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard } 1977a6bc607ca0fe209cfc2f2c38575dc868496fd79Jean Chalard} 198