SentenceLevelAdapter.java revision 86f36003fd4397143bd37938dda029e5707634af
1/* 2 * Copyright (C) 2014 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 17package com.android.inputmethod.latin.spellcheck; 18 19import android.content.res.Resources; 20import android.view.textservice.SentenceSuggestionsInfo; 21import android.view.textservice.SuggestionsInfo; 22import android.view.textservice.TextInfo; 23 24import com.android.inputmethod.compat.TextInfoCompatUtils; 25import com.android.inputmethod.latin.Constants; 26import com.android.inputmethod.latin.settings.SpacingAndPunctuations; 27import com.android.inputmethod.latin.utils.RunInLocale; 28 29import java.util.ArrayList; 30import java.util.Locale; 31 32/** 33 * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in 34 * the framework; maybe that should be protected instead, so that implementers don't have to 35 * rewrite everything for any small change. 36 */ 37public class SentenceLevelAdapter { 38 public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS = 39 new SentenceSuggestionsInfo[] {}; 40 private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null); 41 /** 42 * Container for split TextInfo parameters 43 */ 44 public static class SentenceWordItem { 45 public final TextInfo mTextInfo; 46 public final int mStart; 47 public final int mLength; 48 public SentenceWordItem(TextInfo ti, int start, int end) { 49 mTextInfo = ti; 50 mStart = start; 51 mLength = end - start; 52 } 53 } 54 55 /** 56 * Container for originally queried TextInfo and parameters 57 */ 58 public static class SentenceTextInfoParams { 59 final TextInfo mOriginalTextInfo; 60 final ArrayList<SentenceWordItem> mItems; 61 final int mSize; 62 public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) { 63 mOriginalTextInfo = ti; 64 mItems = items; 65 mSize = items.size(); 66 } 67 } 68 69 private static class WordIterator { 70 private final SpacingAndPunctuations mSpacingAndPunctuations; 71 public WordIterator(final Resources res, final Locale locale) { 72 final RunInLocale<SpacingAndPunctuations> job 73 = new RunInLocale<SpacingAndPunctuations>() { 74 @Override 75 protected SpacingAndPunctuations job(final Resources res) { 76 return new SpacingAndPunctuations(res); 77 } 78 }; 79 mSpacingAndPunctuations = job.runInLocale(res, locale); 80 } 81 82 public int getEndOfWord(final CharSequence sequence, int index) { 83 final int length = sequence.length(); 84 index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1); 85 while (index < length) { 86 final int codePoint = Character.codePointAt(sequence, index); 87 if (mSpacingAndPunctuations.isWordSeparator(codePoint)) { 88 // If it's a period, we want to stop here only if it's followed by another 89 // word separator. In all other cases we stop here. 90 if (Constants.CODE_PERIOD == codePoint) { 91 final int indexOfNextCodePoint = 92 index + Character.charCount(Constants.CODE_PERIOD); 93 if (indexOfNextCodePoint < length 94 && mSpacingAndPunctuations.isWordSeparator( 95 Character.codePointAt(sequence, indexOfNextCodePoint))) { 96 return index; 97 } 98 } else { 99 return index; 100 } 101 } 102 index += Character.charCount(codePoint); 103 } 104 return index; 105 } 106 107 public int getBeginningOfNextWord(final CharSequence sequence, int index) { 108 final int length = sequence.length(); 109 if (index >= length) { 110 return -1; 111 } 112 index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1); 113 while (index < length) { 114 final int codePoint = Character.codePointAt(sequence, index); 115 if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) { 116 return index; 117 } 118 index += Character.charCount(codePoint); 119 } 120 return -1; 121 } 122 } 123 124 private final WordIterator mWordIterator; 125 public SentenceLevelAdapter(final Resources res, final Locale locale) { 126 mWordIterator = new WordIterator(res, locale); 127 } 128 129 public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) { 130 final WordIterator wordIterator = mWordIterator; 131 final CharSequence originalText = 132 TextInfoCompatUtils.getCharSequenceOrString(originalTextInfo); 133 final int cookie = originalTextInfo.getCookie(); 134 final int start = -1; 135 final int end = originalText.length(); 136 final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>(); 137 int wordStart = wordIterator.getBeginningOfNextWord(originalText, start); 138 int wordEnd = wordIterator.getEndOfWord(originalText, wordStart); 139 while (wordStart <= end && wordEnd != -1 && wordStart != -1) { 140 if (wordEnd >= start && wordEnd > wordStart) { 141 CharSequence subSequence = originalText.subSequence(wordStart, wordEnd).toString(); 142 final TextInfo ti = TextInfoCompatUtils.newInstance(subSequence, 0, 143 subSequence.length(), cookie, subSequence.hashCode()); 144 wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd)); 145 } 146 wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd); 147 if (wordStart == -1) { 148 break; 149 } 150 wordEnd = wordIterator.getEndOfWord(originalText, wordStart); 151 } 152 return new SentenceTextInfoParams(originalTextInfo, wordItems); 153 } 154 155 public static SentenceSuggestionsInfo reconstructSuggestions( 156 SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) { 157 if (results == null || results.length == 0) { 158 return null; 159 } 160 if (originalTextInfoParams == null) { 161 return null; 162 } 163 final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie(); 164 final int originalSequence = 165 originalTextInfoParams.mOriginalTextInfo.getSequence(); 166 167 final int querySize = originalTextInfoParams.mSize; 168 final int[] offsets = new int[querySize]; 169 final int[] lengths = new int[querySize]; 170 final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize]; 171 for (int i = 0; i < querySize; ++i) { 172 final SentenceWordItem item = originalTextInfoParams.mItems.get(i); 173 SuggestionsInfo result = null; 174 for (int j = 0; j < results.length; ++j) { 175 final SuggestionsInfo cur = results[j]; 176 if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) { 177 result = cur; 178 result.setCookieAndSequence(originalCookie, originalSequence); 179 break; 180 } 181 } 182 offsets[i] = item.mStart; 183 lengths[i] = item.mLength; 184 reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO; 185 } 186 return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths); 187 } 188} 189