1 /* 2 * Copyright (C) 2013 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; 18 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.Log; 22 import android.view.inputmethod.InputMethodSubtype; 23 24 import com.android.inputmethod.annotations.UsedForTesting; 25 import com.android.inputmethod.keyboard.ProximityInfo; 26 import com.android.inputmethod.latin.PrevWordsInfo.WordInfo; 27 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 28 import com.android.inputmethod.latin.personalization.ContextualDictionary; 29 import com.android.inputmethod.latin.personalization.PersonalizationDataChunk; 30 import com.android.inputmethod.latin.personalization.PersonalizationDictionary; 31 import com.android.inputmethod.latin.personalization.UserHistoryDictionary; 32 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; 33 import com.android.inputmethod.latin.settings.SpacingAndPunctuations; 34 import com.android.inputmethod.latin.utils.DistracterFilter; 35 import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary; 36 import com.android.inputmethod.latin.utils.ExecutorUtils; 37 import com.android.inputmethod.latin.utils.LanguageModelParam; 38 import com.android.inputmethod.latin.utils.SuggestionResults; 39 40 import java.io.File; 41 import java.lang.reflect.InvocationTargetException; 42 import java.lang.reflect.Method; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Locale; 49 import java.util.Map; 50 import java.util.concurrent.ConcurrentHashMap; 51 import java.util.concurrent.CountDownLatch; 52 import java.util.concurrent.TimeUnit; 53 54 // TODO: Consolidate dictionaries in native code. 55 public class DictionaryFacilitator { 56 public static final String TAG = DictionaryFacilitator.class.getSimpleName(); 57 58 // HACK: This threshold is being used when adding a capitalized entry in the User History 59 // dictionary. 60 private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; 61 62 private Dictionaries mDictionaries = new Dictionaries(); 63 private boolean mIsUserDictEnabled = false; 64 private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); 65 // To synchronize assigning mDictionaries to ensure closing dictionaries. 66 private final Object mLock = new Object(); 67 private final DistracterFilter mDistracterFilter; 68 69 private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS = 70 new String[] { 71 Dictionary.TYPE_MAIN, 72 Dictionary.TYPE_USER_HISTORY, 73 Dictionary.TYPE_PERSONALIZATION, 74 Dictionary.TYPE_USER, 75 Dictionary.TYPE_CONTACTS, 76 Dictionary.TYPE_CONTEXTUAL 77 }; 78 79 public static final Map<String, Class<? extends ExpandableBinaryDictionary>> 80 DICT_TYPE_TO_CLASS = new HashMap<>(); 81 82 static { 83 DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class); 84 DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class); 85 DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class); 86 DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class); 87 DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class); 88 } 89 90 private static final String DICT_FACTORY_METHOD_NAME = "getDictionary"; 91 private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES = 92 new Class[] { Context.class, Locale.class, File.class, String.class }; 93 94 private static final String[] SUB_DICT_TYPES = 95 Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */, 96 DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length); 97 98 /** 99 * Class contains dictionaries for a locale. 100 */ 101 private static class Dictionaries { 102 public final Locale mLocale; 103 private Dictionary mMainDict; 104 public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap = 105 new ConcurrentHashMap<>(); 106 107 public Dictionaries() { 108 mLocale = null; 109 } 110 111 public Dictionaries(final Locale locale, final Dictionary mainDict, 112 final Map<String, ExpandableBinaryDictionary> subDicts) { 113 mLocale = locale; 114 // Main dictionary can be asynchronously loaded. 115 setMainDict(mainDict); 116 for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) { 117 setSubDict(entry.getKey(), entry.getValue()); 118 } 119 } 120 121 private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) { 122 if (dict != null) { 123 mSubDictMap.put(dictType, dict); 124 } 125 } 126 127 public void setMainDict(final Dictionary mainDict) { 128 // Close old dictionary if exists. Main dictionary can be assigned multiple times. 129 final Dictionary oldDict = mMainDict; 130 mMainDict = mainDict; 131 if (oldDict != null && mainDict != oldDict) { 132 oldDict.close(); 133 } 134 } 135 136 public Dictionary getDict(final String dictType) { 137 if (Dictionary.TYPE_MAIN.equals(dictType)) { 138 return mMainDict; 139 } else { 140 return getSubDict(dictType); 141 } 142 } 143 144 public ExpandableBinaryDictionary getSubDict(final String dictType) { 145 return mSubDictMap.get(dictType); 146 } 147 148 public boolean hasDict(final String dictType) { 149 if (Dictionary.TYPE_MAIN.equals(dictType)) { 150 return mMainDict != null; 151 } else { 152 return mSubDictMap.containsKey(dictType); 153 } 154 } 155 156 public void closeDict(final String dictType) { 157 final Dictionary dict; 158 if (Dictionary.TYPE_MAIN.equals(dictType)) { 159 dict = mMainDict; 160 } else { 161 dict = mSubDictMap.remove(dictType); 162 } 163 if (dict != null) { 164 dict.close(); 165 } 166 } 167 } 168 169 public interface DictionaryInitializationListener { 170 public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); 171 } 172 173 public DictionaryFacilitator() { 174 mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER; 175 } 176 177 public DictionaryFacilitator(final DistracterFilter distracterFilter) { 178 mDistracterFilter = distracterFilter; 179 } 180 181 public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { 182 mDistracterFilter.updateEnabledSubtypes(enabledSubtypes); 183 } 184 185 public Locale getLocale() { 186 return mDictionaries.mLocale; 187 } 188 189 private static ExpandableBinaryDictionary getSubDict(final String dictType, 190 final Context context, final Locale locale, final File dictFile, 191 final String dictNamePrefix) { 192 final Class<? extends ExpandableBinaryDictionary> dictClass = 193 DICT_TYPE_TO_CLASS.get(dictType); 194 if (dictClass == null) { 195 return null; 196 } 197 try { 198 final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME, 199 DICT_FACTORY_METHOD_ARG_TYPES); 200 final Object dict = factoryMethod.invoke(null /* obj */, 201 new Object[] { context, locale, dictFile, dictNamePrefix }); 202 return (ExpandableBinaryDictionary) dict; 203 } catch (final NoSuchMethodException | SecurityException | IllegalAccessException 204 | IllegalArgumentException | InvocationTargetException e) { 205 Log.e(TAG, "Cannot create dictionary: " + dictType, e); 206 return null; 207 } 208 } 209 210 public void resetDictionaries(final Context context, final Locale newLocale, 211 final boolean useContactsDict, final boolean usePersonalizedDicts, 212 final boolean forceReloadMainDictionary, 213 final DictionaryInitializationListener listener) { 214 resetDictionariesWithDictNamePrefix(context, newLocale, useContactsDict, 215 usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */); 216 } 217 218 public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale, 219 final boolean useContactsDict, final boolean usePersonalizedDicts, 220 final boolean forceReloadMainDictionary, 221 final DictionaryInitializationListener listener, 222 final String dictNamePrefix) { 223 final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale); 224 // We always try to have the main dictionary. Other dictionaries can be unused. 225 final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary; 226 // TODO: Make subDictTypesToUse configurable by resource or a static final list. 227 final HashSet<String> subDictTypesToUse = new HashSet<>(); 228 if (useContactsDict) { 229 subDictTypesToUse.add(Dictionary.TYPE_CONTACTS); 230 } 231 subDictTypesToUse.add(Dictionary.TYPE_USER); 232 if (usePersonalizedDicts) { 233 subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY); 234 subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION); 235 subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL); 236 } 237 238 final Dictionary newMainDict; 239 if (reloadMainDictionary) { 240 // The main dictionary will be asynchronously loaded. 241 newMainDict = null; 242 } else { 243 newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); 244 } 245 246 final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); 247 for (final String dictType : SUB_DICT_TYPES) { 248 if (!subDictTypesToUse.contains(dictType)) { 249 // This dictionary will not be used. 250 continue; 251 } 252 final ExpandableBinaryDictionary dict; 253 if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) { 254 // Continue to use current dictionary. 255 dict = mDictionaries.getSubDict(dictType); 256 } else { 257 // Start to use new dictionary. 258 dict = getSubDict(dictType, context, newLocale, null /* dictFile */, 259 dictNamePrefix); 260 } 261 subDicts.put(dictType, dict); 262 } 263 264 // Replace Dictionaries. 265 final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts); 266 final Dictionaries oldDictionaries; 267 synchronized (mLock) { 268 oldDictionaries = mDictionaries; 269 mDictionaries = newDictionaries; 270 mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context); 271 if (reloadMainDictionary) { 272 asyncReloadMainDictionary(context, newLocale, listener); 273 } 274 } 275 if (listener != null) { 276 listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); 277 } 278 // Clean up old dictionaries. 279 if (reloadMainDictionary) { 280 oldDictionaries.closeDict(Dictionary.TYPE_MAIN); 281 } 282 for (final String dictType : SUB_DICT_TYPES) { 283 if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) { 284 oldDictionaries.closeDict(dictType); 285 } 286 } 287 oldDictionaries.mSubDictMap.clear(); 288 } 289 290 private void asyncReloadMainDictionary(final Context context, final Locale locale, 291 final DictionaryInitializationListener listener) { 292 final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1); 293 mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary; 294 ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { 295 @Override 296 public void run() { 297 final Dictionary mainDict = 298 DictionaryFactory.createMainDictionaryFromManager(context, locale); 299 synchronized (mLock) { 300 if (locale.equals(mDictionaries.mLocale)) { 301 mDictionaries.setMainDict(mainDict); 302 } else { 303 // Dictionary facilitator has been reset for another locale. 304 mainDict.close(); 305 } 306 } 307 if (listener != null) { 308 listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); 309 } 310 latchForWaitingLoadingMainDictionary.countDown(); 311 } 312 }); 313 } 314 315 @UsedForTesting 316 public void resetDictionariesForTesting(final Context context, final Locale locale, 317 final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles, 318 final Map<String, Map<String, String>> additionalDictAttributes) { 319 Dictionary mainDictionary = null; 320 final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); 321 322 for (final String dictType : dictionaryTypes) { 323 if (dictType.equals(Dictionary.TYPE_MAIN)) { 324 mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale); 325 } else { 326 final File dictFile = dictionaryFiles.get(dictType); 327 final ExpandableBinaryDictionary dict = getSubDict( 328 dictType, context, locale, dictFile, "" /* dictNamePrefix */); 329 if (additionalDictAttributes.containsKey(dictType)) { 330 dict.clearAndFlushDictionaryWithAdditionalAttributes( 331 additionalDictAttributes.get(dictType)); 332 } 333 if (dict == null) { 334 throw new RuntimeException("Unknown dictionary type: " + dictType); 335 } 336 dict.reloadDictionaryIfRequired(); 337 dict.waitAllTasksForTests(); 338 subDicts.put(dictType, dict); 339 } 340 } 341 mDictionaries = new Dictionaries(locale, mainDictionary, subDicts); 342 } 343 344 public void closeDictionaries() { 345 final Dictionaries dictionaries; 346 synchronized (mLock) { 347 dictionaries = mDictionaries; 348 mDictionaries = new Dictionaries(); 349 } 350 for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { 351 dictionaries.closeDict(dictType); 352 } 353 mDistracterFilter.close(); 354 } 355 356 @UsedForTesting 357 public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) { 358 return mDictionaries.getSubDict(dictName); 359 } 360 361 // The main dictionary could have been loaded asynchronously. Don't cache the return value 362 // of this method. 363 public boolean hasInitializedMainDictionary() { 364 final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); 365 return mainDict != null && mainDict.isInitialized(); 366 } 367 368 public boolean hasPersonalizationDictionary() { 369 return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION); 370 } 371 372 public void flushPersonalizationDictionary() { 373 final ExpandableBinaryDictionary personalizationDict = 374 mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); 375 if (personalizationDict != null) { 376 personalizationDict.asyncFlushBinaryDictionary(); 377 } 378 } 379 380 public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit) 381 throws InterruptedException { 382 mLatchForWaitingLoadingMainDictionary.await(timeout, unit); 383 } 384 385 @UsedForTesting 386 public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) 387 throws InterruptedException { 388 waitForLoadingMainDictionary(timeout, unit); 389 final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap; 390 for (final ExpandableBinaryDictionary dict : dictMap.values()) { 391 dict.waitAllTasksForTests(); 392 } 393 } 394 395 public boolean isUserDictionaryEnabled() { 396 return mIsUserDictEnabled; 397 } 398 399 public void addWordToUserDictionary(final Context context, final String word) { 400 final Locale locale = getLocale(); 401 if (locale == null) { 402 return; 403 } 404 UserBinaryDictionary.addWordToUserDictionary(context, locale, word); 405 } 406 407 public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, 408 final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds, 409 final boolean blockPotentiallyOffensive) { 410 final Dictionaries dictionaries = mDictionaries; 411 final String[] words = suggestion.split(Constants.WORD_SEPARATOR); 412 PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo; 413 for (int i = 0; i < words.length; i++) { 414 final String currentWord = words[i]; 415 final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false; 416 addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord, 417 wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive); 418 prevWordsInfoForCurrentWord = 419 prevWordsInfoForCurrentWord.getNextPrevWordsInfo(new WordInfo(currentWord)); 420 } 421 } 422 423 private void addWordToUserHistory(final Dictionaries dictionaries, 424 final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized, 425 final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { 426 final ExpandableBinaryDictionary userHistoryDictionary = 427 dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); 428 if (userHistoryDictionary == null) { 429 return; 430 } 431 final int maxFreq = getFrequency(word); 432 if (maxFreq == 0 && blockPotentiallyOffensive) { 433 return; 434 } 435 final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); 436 final String secondWord; 437 if (wasAutoCapitalized) { 438 if (isValidWord(word, false /* ignoreCase */) 439 && !isValidWord(lowerCasedWord, false /* ignoreCase */)) { 440 // If the word was auto-capitalized and exists only as a capitalized word in the 441 // dictionary, then we must not downcase it before registering it. For example, 442 // the name of the contacts in start-of-sentence position would come here with the 443 // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version 444 // of that contact's name which would end up popping in suggestions. 445 secondWord = word; 446 } else { 447 // If however the word is not in the dictionary, or exists as a lower-case word 448 // only, then we consider that was a lower-case word that had been auto-capitalized. 449 secondWord = lowerCasedWord; 450 } 451 } else { 452 // HACK: We'd like to avoid adding the capitalized form of common words to the User 453 // History dictionary in order to avoid suggesting them until the dictionary 454 // consolidation is done. 455 // TODO: Remove this hack when ready. 456 final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ? 457 dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) : 458 Dictionary.NOT_A_PROBABILITY; 459 if (maxFreq < lowerCaseFreqInMainDict 460 && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { 461 // Use lower cased word as the word can be a distracter of the popular word. 462 secondWord = lowerCasedWord; 463 } else { 464 secondWord = word; 465 } 466 } 467 // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". 468 // We don't add words with 0-frequency (assuming they would be profanity etc.). 469 final boolean isValid = maxFreq > 0; 470 UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord, 471 isValid, timeStampInSeconds, 472 new DistracterFilterCheckingIsInDictionary( 473 mDistracterFilter, userHistoryDictionary)); 474 } 475 476 private void removeWord(final String dictName, final String word) { 477 final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName); 478 if (dictionary != null) { 479 dictionary.removeUnigramEntryDynamically(word); 480 } 481 } 482 483 public void removeWordFromPersonalizedDicts(final String word) { 484 removeWord(Dictionary.TYPE_USER_HISTORY, word); 485 removeWord(Dictionary.TYPE_PERSONALIZATION, word); 486 removeWord(Dictionary.TYPE_CONTEXTUAL, word); 487 } 488 489 // TODO: Revise the way to fusion suggestion results. 490 public SuggestionResults getSuggestionResults(final WordComposer composer, 491 final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, 492 final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) { 493 final Dictionaries dictionaries = mDictionaries; 494 final SuggestionResults suggestionResults = new SuggestionResults( 495 dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS, 496 prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence); 497 final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; 498 for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { 499 final Dictionary dictionary = dictionaries.getDict(dictType); 500 if (null == dictionary) continue; 501 final ArrayList<SuggestedWordInfo> dictionarySuggestions = 502 dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, 503 settingsValuesForSuggestion, sessionId, languageWeight); 504 if (null == dictionarySuggestions) continue; 505 suggestionResults.addAll(dictionarySuggestions); 506 if (null != suggestionResults.mRawSuggestions) { 507 suggestionResults.mRawSuggestions.addAll(dictionarySuggestions); 508 } 509 } 510 return suggestionResults; 511 } 512 513 public boolean isValidWord(final String word, final boolean ignoreCase) { 514 if (TextUtils.isEmpty(word)) { 515 return false; 516 } 517 final Dictionaries dictionaries = mDictionaries; 518 if (dictionaries.mLocale == null) { 519 return false; 520 } 521 final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); 522 for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { 523 final Dictionary dictionary = dictionaries.getDict(dictType); 524 // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and 525 // would be immutable once it's finished initializing, but concretely a null test is 526 // probably good enough for the time being. 527 if (null == dictionary) continue; 528 if (dictionary.isValidWord(word) 529 || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) { 530 return true; 531 } 532 } 533 return false; 534 } 535 536 private int getFrequencyInternal(final String word, 537 final boolean isGettingMaxFrequencyOfExactMatches) { 538 if (TextUtils.isEmpty(word)) { 539 return Dictionary.NOT_A_PROBABILITY; 540 } 541 int maxFreq = Dictionary.NOT_A_PROBABILITY; 542 final Dictionaries dictionaries = mDictionaries; 543 for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { 544 final Dictionary dictionary = dictionaries.getDict(dictType); 545 if (dictionary == null) continue; 546 final int tempFreq; 547 if (isGettingMaxFrequencyOfExactMatches) { 548 tempFreq = dictionary.getMaxFrequencyOfExactMatches(word); 549 } else { 550 tempFreq = dictionary.getFrequency(word); 551 } 552 if (tempFreq >= maxFreq) { 553 maxFreq = tempFreq; 554 } 555 } 556 return maxFreq; 557 } 558 559 public int getFrequency(final String word) { 560 return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */); 561 } 562 563 public int getMaxFrequencyOfExactMatches(final String word) { 564 return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */); 565 } 566 567 private void clearSubDictionary(final String dictName) { 568 final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName); 569 if (dictionary != null) { 570 dictionary.clear(); 571 } 572 } 573 574 public void clearUserHistoryDictionary() { 575 clearSubDictionary(Dictionary.TYPE_USER_HISTORY); 576 } 577 578 // This method gets called only when the IME receives a notification to remove the 579 // personalization dictionary. 580 public void clearPersonalizationDictionary() { 581 clearSubDictionary(Dictionary.TYPE_PERSONALIZATION); 582 } 583 584 public void clearContextualDictionary() { 585 clearSubDictionary(Dictionary.TYPE_CONTEXTUAL); 586 } 587 588 public void addEntriesToPersonalizationDictionary( 589 final PersonalizationDataChunk personalizationDataChunk, 590 final SpacingAndPunctuations spacingAndPunctuations, 591 final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { 592 final ExpandableBinaryDictionary personalizationDict = 593 mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); 594 if (personalizationDict == null) { 595 if (callback != null) { 596 callback.onFinished(); 597 } 598 return; 599 } 600 final ArrayList<LanguageModelParam> languageModelParams = 601 LanguageModelParam.createLanguageModelParamsFrom( 602 personalizationDataChunk.mTokens, 603 personalizationDataChunk.mTimestampInSeconds, 604 this /* dictionaryFacilitator */, spacingAndPunctuations, 605 new DistracterFilterCheckingIsInDictionary( 606 mDistracterFilter, personalizationDict)); 607 if (languageModelParams == null || languageModelParams.isEmpty()) { 608 if (callback != null) { 609 callback.onFinished(); 610 } 611 return; 612 } 613 personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback); 614 } 615 616 public void addPhraseToContextualDictionary(final String[] phrase, final int probability, 617 final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) { 618 final ExpandableBinaryDictionary contextualDict = 619 mDictionaries.getSubDict(Dictionary.TYPE_CONTEXTUAL); 620 if (contextualDict == null) { 621 return; 622 } 623 PrevWordsInfo prevWordsInfo = PrevWordsInfo.BEGINNING_OF_SENTENCE; 624 for (int i = 0; i < phrase.length; i++) { 625 final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length); 626 final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase); 627 contextualDict.addUnigramEntryWithCheckingDistracter( 628 subPhraseStr, probability, null /* shortcutTarget */, 629 Dictionary.NOT_A_PROBABILITY /* shortcutFreq */, 630 false /* isNotAWord */, false /* isBlacklisted */, 631 BinaryDictionary.NOT_A_VALID_TIMESTAMP, 632 DistracterFilter.EMPTY_DISTRACTER_FILTER); 633 contextualDict.addNgramEntry(prevWordsInfo, subPhraseStr, 634 bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP); 635 636 if (i < phrase.length - 1) { 637 contextualDict.addUnigramEntryWithCheckingDistracter( 638 phrase[i], probability, null /* shortcutTarget */, 639 Dictionary.NOT_A_PROBABILITY /* shortcutFreq */, 640 false /* isNotAWord */, false /* isBlacklisted */, 641 BinaryDictionary.NOT_A_VALID_TIMESTAMP, 642 DistracterFilter.EMPTY_DISTRACTER_FILTER); 643 contextualDict.addNgramEntry(prevWordsInfo, phrase[i], 644 bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP); 645 } 646 prevWordsInfo = 647 prevWordsInfo.getNextPrevWordsInfo(new PrevWordsInfo.WordInfo(phrase[i])); 648 } 649 } 650 651 public void dumpDictionaryForDebug(final String dictName) { 652 final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName); 653 if (dictToDump == null) { 654 Log.e(TAG, "Cannot dump " + dictName + ". " 655 + "The dictionary is not being used for suggestion or cannot be dumped."); 656 return; 657 } 658 dictToDump.dumpAllWordsForDebug(); 659 } 660 } 661