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