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 17 package com.android.inputmethod.latin.spellcheck; 18 19 import android.os.Binder; 20 import android.text.TextUtils; 21 import android.util.Log; 22 import android.view.textservice.SentenceSuggestionsInfo; 23 import android.view.textservice.SuggestionsInfo; 24 import android.view.textservice.TextInfo; 25 26 import com.android.inputmethod.latin.utils.CollectionUtils; 27 28 import java.util.ArrayList; 29 30 public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { 31 private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName(); 32 private static final boolean DBG = false; 33 private final static String[] EMPTY_STRING_ARRAY = new String[0]; 34 35 public AndroidSpellCheckerSession(AndroidSpellCheckerService service) { 36 super(service); 37 } 38 39 private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti, 40 SentenceSuggestionsInfo ssi) { 41 final String typedText = ti.getText(); 42 if (!typedText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) { 43 return null; 44 } 45 final int N = ssi.getSuggestionsCount(); 46 final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList(); 47 final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList(); 48 final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = 49 CollectionUtils.newArrayList(); 50 String currentWord = null; 51 for (int i = 0; i < N; ++i) { 52 final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); 53 final int flags = si.getSuggestionsAttributes(); 54 if ((flags & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == 0) { 55 continue; 56 } 57 final int offset = ssi.getOffsetAt(i); 58 final int length = ssi.getLengthAt(i); 59 final String subText = typedText.substring(offset, offset + length); 60 final String prevWord = currentWord; 61 currentWord = subText; 62 if (!subText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) { 63 continue; 64 } 65 final String[] splitTexts = 66 subText.split(AndroidSpellCheckerService.SINGLE_QUOTE, -1); 67 if (splitTexts == null || splitTexts.length <= 1) { 68 continue; 69 } 70 final int splitNum = splitTexts.length; 71 for (int j = 0; j < splitNum; ++j) { 72 final String splitText = splitTexts[j]; 73 if (TextUtils.isEmpty(splitText)) { 74 continue; 75 } 76 if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWord) == null) { 77 continue; 78 } 79 final int newLength = splitText.length(); 80 // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO 81 final int newFlags = 0; 82 final SuggestionsInfo newSi = 83 new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY); 84 newSi.setCookieAndSequence(si.getCookie(), si.getSequence()); 85 if (DBG) { 86 Log.d(TAG, "Override and remove old span over: " + splitText + ", " 87 + offset + "," + newLength); 88 } 89 additionalOffsets.add(offset); 90 additionalLengths.add(newLength); 91 additionalSuggestionsInfos.add(newSi); 92 } 93 } 94 final int additionalSize = additionalOffsets.size(); 95 if (additionalSize <= 0) { 96 return null; 97 } 98 final int suggestionsSize = N + additionalSize; 99 final int[] newOffsets = new int[suggestionsSize]; 100 final int[] newLengths = new int[suggestionsSize]; 101 final SuggestionsInfo[] newSuggestionsInfos = new SuggestionsInfo[suggestionsSize]; 102 int i; 103 for (i = 0; i < N; ++i) { 104 newOffsets[i] = ssi.getOffsetAt(i); 105 newLengths[i] = ssi.getLengthAt(i); 106 newSuggestionsInfos[i] = ssi.getSuggestionsInfoAt(i); 107 } 108 for (; i < suggestionsSize; ++i) { 109 newOffsets[i] = additionalOffsets.get(i - N); 110 newLengths[i] = additionalLengths.get(i - N); 111 newSuggestionsInfos[i] = additionalSuggestionsInfos.get(i - N); 112 } 113 return new SentenceSuggestionsInfo(newSuggestionsInfos, newOffsets, newLengths); 114 } 115 116 @Override 117 public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, 118 int suggestionsLimit) { 119 final SentenceSuggestionsInfo[] retval = 120 super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit); 121 if (retval == null || retval.length != textInfos.length) { 122 return retval; 123 } 124 for (int i = 0; i < retval.length; ++i) { 125 final SentenceSuggestionsInfo tempSsi = 126 fixWronglyInvalidatedWordWithSingleQuote(textInfos[i], retval[i]); 127 if (tempSsi != null) { 128 retval[i] = tempSsi; 129 } 130 } 131 return retval; 132 } 133 134 @Override 135 public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, 136 int suggestionsLimit, boolean sequentialWords) { 137 long ident = Binder.clearCallingIdentity(); 138 try { 139 final int length = textInfos.length; 140 final SuggestionsInfo[] retval = new SuggestionsInfo[length]; 141 for (int i = 0; i < length; ++i) { 142 final String prevWord; 143 if (sequentialWords && i > 0) { 144 final String prevWordCandidate = textInfos[i - 1].getText(); 145 // Note that an empty string would be used to indicate the initial word 146 // in the future. 147 prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate; 148 } else { 149 prevWord = null; 150 } 151 retval[i] = onGetSuggestionsInternal(textInfos[i], prevWord, suggestionsLimit); 152 retval[i].setCookieAndSequence(textInfos[i].getCookie(), 153 textInfos[i].getSequence()); 154 } 155 return retval; 156 } finally { 157 Binder.restoreCallingIdentity(ident); 158 } 159 } 160 } 161