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