AndroidSpellCheckerSession.java revision 1467fa0c2642e05aef6f588eef0f704b6da3aee2
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 17package com.android.inputmethod.latin.spellcheck; 18 19import android.content.res.Resources; 20import android.os.Binder; 21import android.text.TextUtils; 22import android.util.Log; 23import android.view.textservice.SentenceSuggestionsInfo; 24import android.view.textservice.SuggestionsInfo; 25import android.view.textservice.TextInfo; 26 27import com.android.inputmethod.latin.PrevWordsInfo; 28 29import java.util.ArrayList; 30import java.util.Locale; 31 32public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { 33 private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName(); 34 private static final boolean DBG = false; 35 private final Resources mResources; 36 private SentenceLevelAdapter mSentenceLevelAdapter; 37 38 public AndroidSpellCheckerSession(AndroidSpellCheckerService service) { 39 super(service); 40 mResources = service.getResources(); 41 } 42 43 private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti, 44 SentenceSuggestionsInfo ssi) { 45 final String typedText = ti.getText(); 46 if (!typedText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) { 47 return null; 48 } 49 final int N = ssi.getSuggestionsCount(); 50 final ArrayList<Integer> additionalOffsets = new ArrayList<>(); 51 final ArrayList<Integer> additionalLengths = new ArrayList<>(); 52 final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>(); 53 String currentWord = null; 54 for (int i = 0; i < N; ++i) { 55 final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); 56 final int flags = si.getSuggestionsAttributes(); 57 if ((flags & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == 0) { 58 continue; 59 } 60 final int offset = ssi.getOffsetAt(i); 61 final int length = ssi.getLengthAt(i); 62 final String subText = typedText.substring(offset, offset + length); 63 final PrevWordsInfo prevWordsInfo = 64 new PrevWordsInfo(new PrevWordsInfo.WordInfo(currentWord)); 65 currentWord = subText; 66 if (!subText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) { 67 continue; 68 } 69 final String[] splitTexts = 70 subText.split(AndroidSpellCheckerService.SINGLE_QUOTE, -1); 71 if (splitTexts == null || splitTexts.length <= 1) { 72 continue; 73 } 74 final int splitNum = splitTexts.length; 75 for (int j = 0; j < splitNum; ++j) { 76 final String splitText = splitTexts[j]; 77 if (TextUtils.isEmpty(splitText)) { 78 continue; 79 } 80 if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWordsInfo) == null) { 81 continue; 82 } 83 final int newLength = splitText.length(); 84 // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO 85 final int newFlags = 0; 86 final SuggestionsInfo newSi = 87 new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY); 88 newSi.setCookieAndSequence(si.getCookie(), si.getSequence()); 89 if (DBG) { 90 Log.d(TAG, "Override and remove old span over: " + splitText + ", " 91 + offset + "," + newLength); 92 } 93 additionalOffsets.add(offset); 94 additionalLengths.add(newLength); 95 additionalSuggestionsInfos.add(newSi); 96 } 97 } 98 final int additionalSize = additionalOffsets.size(); 99 if (additionalSize <= 0) { 100 return null; 101 } 102 final int suggestionsSize = N + additionalSize; 103 final int[] newOffsets = new int[suggestionsSize]; 104 final int[] newLengths = new int[suggestionsSize]; 105 final SuggestionsInfo[] newSuggestionsInfos = new SuggestionsInfo[suggestionsSize]; 106 int i; 107 for (i = 0; i < N; ++i) { 108 newOffsets[i] = ssi.getOffsetAt(i); 109 newLengths[i] = ssi.getLengthAt(i); 110 newSuggestionsInfos[i] = ssi.getSuggestionsInfoAt(i); 111 } 112 for (; i < suggestionsSize; ++i) { 113 newOffsets[i] = additionalOffsets.get(i - N); 114 newLengths[i] = additionalLengths.get(i - N); 115 newSuggestionsInfos[i] = additionalSuggestionsInfos.get(i - N); 116 } 117 return new SentenceSuggestionsInfo(newSuggestionsInfos, newOffsets, newLengths); 118 } 119 120 @Override 121 public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, 122 int suggestionsLimit) { 123 final SentenceSuggestionsInfo[] retval = splitAndSuggest(textInfos, suggestionsLimit); 124 if (retval == null || retval.length != textInfos.length) { 125 return retval; 126 } 127 for (int i = 0; i < retval.length; ++i) { 128 final SentenceSuggestionsInfo tempSsi = 129 fixWronglyInvalidatedWordWithSingleQuote(textInfos[i], retval[i]); 130 if (tempSsi != null) { 131 retval[i] = tempSsi; 132 } 133 } 134 return retval; 135 } 136 137 /** 138 * Get sentence suggestions for specified texts in an array of TextInfo. This is taken from 139 * SpellCheckerService#onGetSentenceSuggestionsMultiple that we can't use because it's 140 * using private variables. 141 * The default implementation splits the input text to words and returns 142 * {@link SentenceSuggestionsInfo} which contains suggestions for each word. 143 * This function will run on the incoming IPC thread. 144 * So, this is not called on the main thread, 145 * but will be called in series on another thread. 146 * @param textInfos an array of the text metadata 147 * @param suggestionsLimit the maximum number of suggestions to be returned 148 * @return an array of {@link SentenceSuggestionsInfo} returned by 149 * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} 150 */ 151 private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) { 152 if (textInfos == null || textInfos.length == 0) { 153 return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS; 154 } 155 SentenceLevelAdapter sentenceLevelAdapter; 156 synchronized(this) { 157 sentenceLevelAdapter = mSentenceLevelAdapter; 158 if (sentenceLevelAdapter == null) { 159 final String localeStr = getLocale(); 160 if (!TextUtils.isEmpty(localeStr)) { 161 sentenceLevelAdapter = new SentenceLevelAdapter(mResources, 162 new Locale(localeStr)); 163 mSentenceLevelAdapter = sentenceLevelAdapter; 164 } 165 } 166 } 167 if (sentenceLevelAdapter == null) { 168 return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS; 169 } 170 final int infosSize = textInfos.length; 171 final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize]; 172 for (int i = 0; i < infosSize; ++i) { 173 final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams = 174 sentenceLevelAdapter.getSplitWords(textInfos[i]); 175 final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems = 176 textInfoParams.mItems; 177 final int itemsSize = mItems.size(); 178 final TextInfo[] splitTextInfos = new TextInfo[itemsSize]; 179 for (int j = 0; j < itemsSize; ++j) { 180 splitTextInfos[j] = mItems.get(j).mTextInfo; 181 } 182 retval[i] = SentenceLevelAdapter.reconstructSuggestions( 183 textInfoParams, onGetSuggestionsMultiple( 184 splitTextInfos, suggestionsLimit, true)); 185 } 186 return retval; 187 } 188 189 @Override 190 public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, 191 int suggestionsLimit, boolean sequentialWords) { 192 long ident = Binder.clearCallingIdentity(); 193 try { 194 final int length = textInfos.length; 195 final SuggestionsInfo[] retval = new SuggestionsInfo[length]; 196 for (int i = 0; i < length; ++i) { 197 final String prevWord; 198 if (sequentialWords && i > 0) { 199 final String prevWordCandidate = textInfos[i - 1].getText(); 200 // Note that an empty string would be used to indicate the initial word 201 // in the future. 202 prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate; 203 } else { 204 prevWord = null; 205 } 206 final PrevWordsInfo prevWordsInfo = 207 new PrevWordsInfo(new PrevWordsInfo.WordInfo(prevWord)); 208 retval[i] = onGetSuggestionsInternal(textInfos[i], prevWordsInfo, suggestionsLimit); 209 retval[i].setCookieAndSequence(textInfos[i].getCookie(), 210 textInfos[i].getSequence()); 211 } 212 return retval; 213 } finally { 214 Binder.restoreCallingIdentity(ident); 215 } 216 } 217} 218