1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.Log; 22 23 import com.android.inputmethod.keyboard.ProximityInfo; 24 25 import java.io.File; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.Locale; 31 import java.util.Map; 32 import java.util.Set; 33 34 /** 35 * This class loads a dictionary and provides a list of suggestions for a given sequence of 36 * characters. This includes corrections and completions. 37 */ 38 public class Suggest implements Dictionary.WordCallback { 39 40 public static final String TAG = Suggest.class.getSimpleName(); 41 42 public static final int APPROX_MAX_WORD_LENGTH = 32; 43 44 public static final int CORRECTION_NONE = 0; 45 public static final int CORRECTION_BASIC = 1; 46 public static final int CORRECTION_FULL = 2; 47 public static final int CORRECTION_FULL_BIGRAM = 3; 48 49 /** 50 * Words that appear in both bigram and unigram data gets multiplier ranging from 51 * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from 52 * bigram data. 53 */ 54 public static final double BIGRAM_MULTIPLIER_MIN = 1.2; 55 public static final double BIGRAM_MULTIPLIER_MAX = 1.5; 56 57 /** 58 * Maximum possible bigram frequency. Will depend on how many bits are being used in data 59 * structure. Maximum bigram frequency will get the BIGRAM_MULTIPLIER_MAX as the multiplier. 60 */ 61 public static final int MAXIMUM_BIGRAM_FREQUENCY = 127; 62 63 // It seems the following values are only used for logging. 64 public static final int DIC_USER_TYPED = 0; 65 public static final int DIC_MAIN = 1; 66 public static final int DIC_USER = 2; 67 public static final int DIC_USER_UNIGRAM = 3; 68 public static final int DIC_CONTACTS = 4; 69 public static final int DIC_USER_BIGRAM = 5; 70 public static final int DIC_WHITELIST = 6; 71 // If you add a type of dictionary, increment DIC_TYPE_LAST_ID 72 // TODO: this value seems unused. Remove it? 73 public static final int DIC_TYPE_LAST_ID = 6; 74 public static final String DICT_KEY_MAIN = "main"; 75 public static final String DICT_KEY_CONTACTS = "contacts"; 76 // User dictionary, the system-managed one. 77 public static final String DICT_KEY_USER = "user"; 78 // User unigram dictionary, internal to LatinIME 79 public static final String DICT_KEY_USER_UNIGRAM = "user_unigram"; 80 // User bigram dictionary, internal to LatinIME 81 public static final String DICT_KEY_USER_BIGRAM = "user_bigram"; 82 public static final String DICT_KEY_WHITELIST ="whitelist"; 83 84 private static final boolean DBG = LatinImeLogger.sDBG; 85 86 private AutoCorrection mAutoCorrection; 87 88 private Dictionary mMainDict; 89 private ContactsDictionary mContactsDict; 90 private WhitelistDictionary mWhiteListDictionary; 91 private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>(); 92 private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>(); 93 94 private int mPrefMaxSuggestions = 18; 95 96 private static final int PREF_MAX_BIGRAMS = 60; 97 98 private double mAutoCorrectionThreshold; 99 private int[] mScores = new int[mPrefMaxSuggestions]; 100 private int[] mBigramScores = new int[PREF_MAX_BIGRAMS]; 101 102 private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); 103 ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>(); 104 private CharSequence mTypedWord; 105 106 // TODO: Remove these member variables by passing more context to addWord() callback method 107 private boolean mIsFirstCharCapitalized; 108 private boolean mIsAllUpperCase; 109 110 private int mCorrectionMode = CORRECTION_BASIC; 111 Suggest(final Context context, final int dictionaryResId, final Locale locale)112 public Suggest(final Context context, final int dictionaryResId, final Locale locale) { 113 initAsynchronously(context, dictionaryResId, locale); 114 } 115 Suggest(final Context context, final File dictionary, final long startOffset, final long length, final Flag[] flagArray, final Locale locale)116 /* package for test */ Suggest(final Context context, final File dictionary, 117 final long startOffset, final long length, final Flag[] flagArray, 118 final Locale locale) { 119 initSynchronously(null, DictionaryFactory.createDictionaryForTest(context, dictionary, 120 startOffset, length, flagArray), locale); 121 } 122 initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale)123 private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) { 124 mWhiteListDictionary = new WhitelistDictionary(context, locale); 125 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary); 126 mAutoCorrection = new AutoCorrection(); 127 StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength()); 128 } 129 initAsynchronously(final Context context, final int dictionaryResId, final Locale locale)130 private void initAsynchronously(final Context context, final int dictionaryResId, 131 final Locale locale) { 132 resetMainDict(context, dictionaryResId, locale); 133 134 // TODO: read the whitelist and init the pool asynchronously too. 135 // initPool should be done asynchronously now that the pool is thread-safe. 136 initWhitelistAndAutocorrectAndPool(context, locale); 137 } 138 initSynchronously(final Context context, final Dictionary mainDict, final Locale locale)139 private void initSynchronously(final Context context, final Dictionary mainDict, 140 final Locale locale) { 141 mMainDict = mainDict; 142 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict); 143 addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict); 144 initWhitelistAndAutocorrectAndPool(context, locale); 145 } 146 addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key, Dictionary dict)147 private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key, 148 Dictionary dict) { 149 final Dictionary oldDict = (dict == null) 150 ? dictionaries.remove(key) 151 : dictionaries.put(key, dict); 152 if (oldDict != null && dict != oldDict) { 153 oldDict.close(); 154 } 155 } 156 resetMainDict(final Context context, final int dictionaryResId, final Locale locale)157 public void resetMainDict(final Context context, final int dictionaryResId, 158 final Locale locale) { 159 mMainDict = null; 160 new Thread("InitializeBinaryDictionary") { 161 @Override 162 public void run() { 163 final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager( 164 context, locale, dictionaryResId); 165 mMainDict = newMainDict; 166 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict); 167 addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict); 168 } 169 }.start(); 170 } 171 getCorrectionMode()172 public int getCorrectionMode() { 173 return mCorrectionMode; 174 } 175 setCorrectionMode(int mode)176 public void setCorrectionMode(int mode) { 177 mCorrectionMode = mode; 178 } 179 180 // The main dictionary could have been loaded asynchronously. Don't cache the return value 181 // of this method. hasMainDictionary()182 public boolean hasMainDictionary() { 183 return mMainDict != null; 184 } 185 getContactsDictionary()186 public ContactsDictionary getContactsDictionary() { 187 return mContactsDict; 188 } 189 getUnigramDictionaries()190 public Map<String, Dictionary> getUnigramDictionaries() { 191 return mUnigramDictionaries; 192 } 193 getApproxMaxWordLength()194 public int getApproxMaxWordLength() { 195 return APPROX_MAX_WORD_LENGTH; 196 } 197 198 /** 199 * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted 200 * before the main dictionary, if set. This refers to the system-managed user dictionary. 201 */ setUserDictionary(Dictionary userDictionary)202 public void setUserDictionary(Dictionary userDictionary) { 203 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary); 204 } 205 206 /** 207 * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove 208 * the contacts dictionary by passing null to this method. In this case no contacts dictionary 209 * won't be used. 210 */ setContactsDictionary(ContactsDictionary contactsDictionary)211 public void setContactsDictionary(ContactsDictionary contactsDictionary) { 212 mContactsDict = contactsDictionary; 213 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); 214 addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); 215 } 216 setUserUnigramDictionary(Dictionary userUnigramDictionary)217 public void setUserUnigramDictionary(Dictionary userUnigramDictionary) { 218 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_UNIGRAM, userUnigramDictionary); 219 } 220 setUserBigramDictionary(Dictionary userBigramDictionary)221 public void setUserBigramDictionary(Dictionary userBigramDictionary) { 222 addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_BIGRAM, userBigramDictionary); 223 } 224 setAutoCorrectionThreshold(double threshold)225 public void setAutoCorrectionThreshold(double threshold) { 226 mAutoCorrectionThreshold = threshold; 227 } 228 isAggressiveAutoCorrectionMode()229 public boolean isAggressiveAutoCorrectionMode() { 230 return (mAutoCorrectionThreshold == 0); 231 } 232 233 /** 234 * Number of suggestions to generate from the input key sequence. This has 235 * to be a number between 1 and 100 (inclusive). 236 * @param maxSuggestions 237 * @throws IllegalArgumentException if the number is out of range 238 */ setMaxSuggestions(int maxSuggestions)239 public void setMaxSuggestions(int maxSuggestions) { 240 if (maxSuggestions < 1 || maxSuggestions > 100) { 241 throw new IllegalArgumentException("maxSuggestions must be between 1 and 100"); 242 } 243 mPrefMaxSuggestions = maxSuggestions; 244 mScores = new int[mPrefMaxSuggestions]; 245 mBigramScores = new int[PREF_MAX_BIGRAMS]; 246 collectGarbage(mSuggestions, mPrefMaxSuggestions); 247 StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength()); 248 } 249 250 /** 251 * Returns a object which represents suggested words that match the list of character codes 252 * passed in. This object contents will be overwritten the next time this function is called. 253 * @param wordComposer contains what is currently being typed 254 * @param prevWordForBigram previous word (used only for bigram) 255 * @return suggested words object. 256 */ getSuggestions(final WordComposer wordComposer, final CharSequence prevWordForBigram, final ProximityInfo proximityInfo)257 public SuggestedWords getSuggestions(final WordComposer wordComposer, 258 final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) { 259 return getSuggestedWordBuilder(wordComposer, prevWordForBigram, 260 proximityInfo).build(); 261 } 262 capitalizeWord(boolean all, boolean first, CharSequence word)263 private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) { 264 if (TextUtils.isEmpty(word) || !(all || first)) return word; 265 final int wordLength = word.length(); 266 final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength()); 267 // TODO: Must pay attention to locale when changing case. 268 if (all) { 269 sb.append(word.toString().toUpperCase()); 270 } else if (first) { 271 sb.append(Character.toUpperCase(word.charAt(0))); 272 if (wordLength > 1) { 273 sb.append(word.subSequence(1, wordLength)); 274 } 275 } 276 return sb; 277 } 278 addBigramToSuggestions(CharSequence bigram)279 protected void addBigramToSuggestions(CharSequence bigram) { 280 // TODO: Try to be a little more shrewd with resource allocation. 281 // At the moment we copy this object because the StringBuilders are pooled (see 282 // StringBuilderPool.java) and when we are finished using mSuggestions and 283 // mBigramSuggestions we will take everything from both and insert them back in the 284 // pool, so we can't allow the same object to be in both lists at the same time. 285 final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength()); 286 sb.append(bigram); 287 mSuggestions.add(sb); 288 } 289 290 // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder getSuggestedWordBuilder( final WordComposer wordComposer, CharSequence prevWordForBigram, final ProximityInfo proximityInfo)291 public SuggestedWords.Builder getSuggestedWordBuilder( 292 final WordComposer wordComposer, CharSequence prevWordForBigram, 293 final ProximityInfo proximityInfo) { 294 LatinImeLogger.onStartSuggestion(prevWordForBigram); 295 mAutoCorrection.init(); 296 mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); 297 mIsAllUpperCase = wordComposer.isAllUpperCase(); 298 collectGarbage(mSuggestions, mPrefMaxSuggestions); 299 Arrays.fill(mScores, 0); 300 301 // Save a lowercase version of the original word 302 String typedWord = wordComposer.getTypedWord(); 303 if (typedWord != null) { 304 // Treating USER_TYPED as UNIGRAM suggestion for logging now. 305 LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, 306 Dictionary.DataType.UNIGRAM); 307 } 308 mTypedWord = typedWord; 309 310 if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM 311 || mCorrectionMode == CORRECTION_BASIC)) { 312 // At first character typed, search only the bigrams 313 Arrays.fill(mBigramScores, 0); 314 collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); 315 316 if (!TextUtils.isEmpty(prevWordForBigram)) { 317 CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase(); 318 if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) { 319 prevWordForBigram = lowerPrevWord; 320 } 321 for (final Dictionary dictionary : mBigramDictionaries.values()) { 322 dictionary.getBigrams(wordComposer, prevWordForBigram, this); 323 } 324 if (TextUtils.isEmpty(typedWord)) { 325 // Nothing entered: return all bigrams for the previous word 326 int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions); 327 for (int i = 0; i < insertCount; ++i) { 328 addBigramToSuggestions(mBigramSuggestions.get(i)); 329 } 330 } else { 331 // Word entered: return only bigrams that match the first char of the typed word 332 @SuppressWarnings("null") 333 final char currentChar = typedWord.charAt(0); 334 // TODO: Must pay attention to locale when changing case. 335 final char currentCharUpper = Character.toUpperCase(currentChar); 336 int count = 0; 337 final int bigramSuggestionSize = mBigramSuggestions.size(); 338 for (int i = 0; i < bigramSuggestionSize; i++) { 339 final CharSequence bigramSuggestion = mBigramSuggestions.get(i); 340 final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0); 341 if (bigramSuggestionFirstChar == currentChar 342 || bigramSuggestionFirstChar == currentCharUpper) { 343 addBigramToSuggestions(bigramSuggestion); 344 if (++count > mPrefMaxSuggestions) break; 345 } 346 } 347 } 348 } 349 350 } else if (wordComposer.size() > 1) { 351 // At second character typed, search the unigrams (scores being affected by bigrams) 352 for (final String key : mUnigramDictionaries.keySet()) { 353 // Skip UserUnigramDictionary and WhitelistDictionary to lookup 354 if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST)) 355 continue; 356 final Dictionary dictionary = mUnigramDictionaries.get(key); 357 dictionary.getWords(wordComposer, this, proximityInfo); 358 } 359 } 360 final String typedWordString = typedWord == null ? null : typedWord.toString(); 361 362 CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized, 363 mWhiteListDictionary.getWhitelistedWord(typedWordString)); 364 365 mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer, 366 mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode, 367 whitelistedWord); 368 369 if (whitelistedWord != null) { 370 mSuggestions.add(0, whitelistedWord); 371 } 372 373 if (typedWord != null) { 374 mSuggestions.add(0, typedWordString); 375 } 376 Utils.removeDupes(mSuggestions); 377 378 if (DBG) { 379 double normalizedScore = mAutoCorrection.getNormalizedScore(); 380 ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList = 381 new ArrayList<SuggestedWords.SuggestedWordInfo>(); 382 scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); 383 for (int i = 0; i < mScores.length; ++i) { 384 if (normalizedScore > 0) { 385 final String scoreThreshold = String.format("%d (%4.2f)", mScores[i], 386 normalizedScore); 387 scoreInfoList.add( 388 new SuggestedWords.SuggestedWordInfo(scoreThreshold, false)); 389 normalizedScore = 0.0; 390 } else { 391 final String score = Integer.toString(mScores[i]); 392 scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false)); 393 } 394 } 395 for (int i = mScores.length; i < mSuggestions.size(); ++i) { 396 scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); 397 } 398 return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList); 399 } 400 return new SuggestedWords.Builder().addWords(mSuggestions, null); 401 } 402 hasAutoCorrection()403 public boolean hasAutoCorrection() { 404 return mAutoCorrection.hasAutoCorrection(); 405 } 406 407 @Override addWord(final char[] word, final int offset, final int length, int score, final int dicTypeId, final Dictionary.DataType dataType)408 public boolean addWord(final char[] word, final int offset, final int length, int score, 409 final int dicTypeId, final Dictionary.DataType dataType) { 410 Dictionary.DataType dataTypeForLog = dataType; 411 final ArrayList<CharSequence> suggestions; 412 final int[] sortedScores; 413 final int prefMaxSuggestions; 414 if(dataType == Dictionary.DataType.BIGRAM) { 415 suggestions = mBigramSuggestions; 416 sortedScores = mBigramScores; 417 prefMaxSuggestions = PREF_MAX_BIGRAMS; 418 } else { 419 suggestions = mSuggestions; 420 sortedScores = mScores; 421 prefMaxSuggestions = mPrefMaxSuggestions; 422 } 423 424 int pos = 0; 425 426 // Check if it's the same word, only caps are different 427 if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) { 428 // TODO: remove this surrounding if clause and move this logic to 429 // getSuggestedWordBuilder. 430 if (suggestions.size() > 0) { 431 final String currentHighestWord = suggestions.get(0).toString(); 432 // If the current highest word is also equal to typed word, we need to compare 433 // frequency to determine the insertion position. This does not ensure strictly 434 // correct ordering, but ensures the top score is on top which is enough for 435 // removing duplicates correctly. 436 if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length) 437 && score <= sortedScores[0]) { 438 pos = 1; 439 } 440 } 441 } else { 442 if (dataType == Dictionary.DataType.UNIGRAM) { 443 // Check if the word was already added before (by bigram data) 444 int bigramSuggestion = searchBigramSuggestion(word,offset,length); 445 if(bigramSuggestion >= 0) { 446 dataTypeForLog = Dictionary.DataType.BIGRAM; 447 // turn freq from bigram into multiplier specified above 448 double multiplier = (((double) mBigramScores[bigramSuggestion]) 449 / MAXIMUM_BIGRAM_FREQUENCY) 450 * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN) 451 + BIGRAM_MULTIPLIER_MIN; 452 /* Log.d(TAG,"bigram num: " + bigramSuggestion 453 + " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString() 454 + " currentScore: " + score + " bigramScore: " 455 + mBigramScores[bigramSuggestion] 456 + " multiplier: " + multiplier); */ 457 score = (int)Math.round((score * multiplier)); 458 } 459 } 460 461 // Check the last one's score and bail 462 if (sortedScores[prefMaxSuggestions - 1] >= score) return true; 463 while (pos < prefMaxSuggestions) { 464 if (sortedScores[pos] < score 465 || (sortedScores[pos] == score && length < suggestions.get(pos).length())) { 466 break; 467 } 468 pos++; 469 } 470 } 471 if (pos >= prefMaxSuggestions) { 472 return true; 473 } 474 475 System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1); 476 sortedScores[pos] = score; 477 final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength()); 478 // TODO: Must pay attention to locale when changing case. 479 if (mIsAllUpperCase) { 480 sb.append(new String(word, offset, length).toUpperCase()); 481 } else if (mIsFirstCharCapitalized) { 482 sb.append(Character.toUpperCase(word[offset])); 483 if (length > 1) { 484 sb.append(word, offset + 1, length - 1); 485 } 486 } else { 487 sb.append(word, offset, length); 488 } 489 suggestions.add(pos, sb); 490 if (suggestions.size() > prefMaxSuggestions) { 491 final CharSequence garbage = suggestions.remove(prefMaxSuggestions); 492 if (garbage instanceof StringBuilder) { 493 StringBuilderPool.recycle((StringBuilder)garbage); 494 } 495 } else { 496 LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog); 497 } 498 return true; 499 } 500 searchBigramSuggestion(final char[] word, final int offset, final int length)501 private int searchBigramSuggestion(final char[] word, final int offset, final int length) { 502 // TODO This is almost O(n^2). Might need fix. 503 // search whether the word appeared in bigram data 504 int bigramSuggestSize = mBigramSuggestions.size(); 505 for(int i = 0; i < bigramSuggestSize; i++) { 506 if(mBigramSuggestions.get(i).length() == length) { 507 boolean chk = true; 508 for(int j = 0; j < length; j++) { 509 if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) { 510 chk = false; 511 break; 512 } 513 } 514 if(chk) return i; 515 } 516 } 517 518 return -1; 519 } 520 collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions)521 private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) { 522 int poolSize = StringBuilderPool.getSize(); 523 int garbageSize = suggestions.size(); 524 while (poolSize < prefMaxSuggestions && garbageSize > 0) { 525 final CharSequence garbage = suggestions.get(garbageSize - 1); 526 if (garbage instanceof StringBuilder) { 527 StringBuilderPool.recycle((StringBuilder)garbage); 528 poolSize++; 529 } 530 garbageSize--; 531 } 532 if (poolSize == prefMaxSuggestions + 1) { 533 Log.w("Suggest", "String pool got too big: " + poolSize); 534 } 535 suggestions.clear(); 536 } 537 close()538 public void close() { 539 final Set<Dictionary> dictionaries = new HashSet<Dictionary>(); 540 dictionaries.addAll(mUnigramDictionaries.values()); 541 dictionaries.addAll(mBigramDictionaries.values()); 542 for (final Dictionary dictionary : dictionaries) { 543 dictionary.close(); 544 } 545 mMainDict = null; 546 } 547 } 548