Exemplo n.º 1
0
 public void getSuggestedWords(
     final WordComposer wordComposer,
     final String prevWordForBigram,
     final ProximityInfo proximityInfo,
     final boolean blockOffensiveWords,
     final boolean isCorrectionEnabled,
     final int[] additionalFeaturesOptions,
     final int sessionId,
     final int sequenceNumber,
     final OnGetSuggestedWordsCallback callback) {
   LatinImeLogger.onStartSuggestion(prevWordForBigram);
   if (wordComposer.isBatchMode()) {
     getSuggestedWordsForBatchInput(
         wordComposer,
         prevWordForBigram,
         proximityInfo,
         blockOffensiveWords,
         additionalFeaturesOptions,
         sessionId,
         sequenceNumber,
         callback);
   } else {
     getSuggestedWordsForTypingInput(
         wordComposer,
         prevWordForBigram,
         proximityInfo,
         blockOffensiveWords,
         isCorrectionEnabled,
         additionalFeaturesOptions,
         sequenceNumber,
         callback);
   }
 }
Exemplo n.º 2
0
 public static void backspace() {
   if (sState == State.ACCEPTED_DEFAULT) {
     sState = State.UNDO_COMMIT;
     sAutoSuggestUndoneCount++;
     LatinImeLogger.logOnAutoSuggestionCanceled();
   } else if (sState == State.UNDO_COMMIT) {
     sState = State.IN_WORD;
   }
   sBackspaceCount++;
   displayState();
 }
Exemplo n.º 3
0
 public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) {
   if (typedWord == null) return;
   if (!typedWord.equals(actualWord)) {
     sAutoSuggestCount++;
   }
   sTypedChars += typedWord.length();
   sActualChars += actualWord.length();
   sState = State.ACCEPTED_DEFAULT;
   LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
   displayState();
 }
Exemplo n.º 4
0
  public boolean addWord(
      final char[] word,
      final int offset,
      final int length,
      int freq,
      final int dicTypeId,
      final Dictionary.DataType dataType) {
    Dictionary.DataType dataTypeForLog = dataType;
    ArrayList<CharSequence> suggestions;
    int[] priorities;
    int prefMaxSuggestions;
    if (dataType == Dictionary.DataType.BIGRAM) {
      suggestions = mBigramSuggestions;
      priorities = mBigramPriorities;
      prefMaxSuggestions = PREF_MAX_BIGRAMS;
    } else {
      suggestions = mSuggestions;
      priorities = mPriorities;
      prefMaxSuggestions = mPrefMaxSuggestions;
    }

    int pos = 0;

    // Check if it's the same word, only caps are different
    if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
      pos = 0;
    } else {
      if (dataType == Dictionary.DataType.UNIGRAM) {
        // Check if the word was already added before (by bigram data)
        int bigramSuggestion = searchBigramSuggestion(word, offset, length);
        if (bigramSuggestion >= 0) {
          dataTypeForLog = Dictionary.DataType.BIGRAM;
          // turn freq from bigram into multiplier specified above
          double multiplier =
              (((double) mBigramPriorities[bigramSuggestion]) / MAXIMUM_BIGRAM_FREQUENCY)
                      * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
                  + BIGRAM_MULTIPLIER_MIN;
          /* Log.d(TAG,"bigram num: " + bigramSuggestion
          + "  wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
          + "  currentPriority: " + freq + "  bigramPriority: "
          + mBigramPriorities[bigramSuggestion]
          + "  multiplier: " + multiplier); */
          freq = (int) Math.round((freq * multiplier));
        }
      }

      // Check the last one's priority and bail
      if (priorities[prefMaxSuggestions - 1] >= freq) return true;
      while (pos < prefMaxSuggestions) {
        if (priorities[pos] < freq
            || (priorities[pos] == freq && length < suggestions.get(pos).length())) {
          break;
        }
        pos++;
      }
    }
    if (pos >= prefMaxSuggestions) {
      return true;
    }

    System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
    priorities[pos] = freq;
    int poolSize = mStringPool.size();
    StringBuilder sb =
        poolSize > 0
            ? (StringBuilder) mStringPool.remove(poolSize - 1)
            : new StringBuilder(getApproxMaxWordLength());
    sb.setLength(0);
    if (mIsAllUpperCase) {
      sb.append(new String(word, offset, length).toUpperCase());
    } else if (mIsFirstCharCapitalized) {
      sb.append(Character.toUpperCase(word[offset]));
      if (length > 1) {
        sb.append(word, offset + 1, length - 1);
      }
    } else {
      sb.append(word, offset, length);
    }
    suggestions.add(pos, sb);
    if (suggestions.size() > prefMaxSuggestions) {
      CharSequence garbage = suggestions.remove(prefMaxSuggestions);
      if (garbage instanceof StringBuilder) {
        mStringPool.add(garbage);
      }
    } else {
      LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
    }
    return true;
  }
Exemplo n.º 5
0
  /**
   * Returns a list of words that match the list of character codes passed in. This list will be
   * overwritten the next time this function is called.
   *
   * @param view a view for retrieving the context for AutoText
   * @param wordComposer contains what is currently being typed
   * @param prevWordForBigram previous word (used only for bigram)
   * @return list of suggestions.
   */
  public List<CharSequence> getSuggestions(
      View view,
      WordComposer wordComposer,
      boolean includeTypedWordIfValid,
      CharSequence prevWordForBigram) {
    LatinImeLogger.onStartSuggestion(prevWordForBigram);
    mHaveCorrection = false;
    mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
    mIsAllUpperCase = wordComposer.isAllUpperCase();
    collectGarbage(mSuggestions, mPrefMaxSuggestions);
    Arrays.fill(mPriorities, 0);
    Arrays.fill(mNextLettersFrequencies, 0);

    // Save a lowercase version of the original word
    mOriginalWord = wordComposer.getTypedWord();
    if (mOriginalWord != null) {
      final String mOriginalWordString = mOriginalWord.toString();
      mOriginalWord = mOriginalWordString;
      mLowerOriginalWord = mOriginalWordString.toLowerCase();
      // Treating USER_TYPED as UNIGRAM suggestion for logging now.
      LatinImeLogger.onAddSuggestedWord(
          mOriginalWordString, Suggest.DIC_USER_TYPED, Dictionary.DataType.UNIGRAM);
    } else {
      mLowerOriginalWord = "";
    }

    if (wordComposer.size() == 1
        && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) {
      // At first character typed, search only the bigrams
      Arrays.fill(mBigramPriorities, 0);
      collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);

      if (!TextUtils.isEmpty(prevWordForBigram)) {
        CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
        if (mMainDict.isValidWord(lowerPrevWord)) {
          prevWordForBigram = lowerPrevWord;
        }
        if (mUserBigramDictionary != null) {
          mUserBigramDictionary.getBigrams(
              wordComposer, prevWordForBigram, this, mNextLettersFrequencies);
        }
        if (mContactsDictionary != null) {
          mContactsDictionary.getBigrams(
              wordComposer, prevWordForBigram, this, mNextLettersFrequencies);
        }
        if (mMainDict != null) {
          mMainDict.getBigrams(wordComposer, prevWordForBigram, this, mNextLettersFrequencies);
        }
        char currentChar = wordComposer.getTypedWord().charAt(0);
        char currentCharUpper = Character.toUpperCase(currentChar);
        int count = 0;
        int bigramSuggestionSize = mBigramSuggestions.size();
        for (int i = 0; i < bigramSuggestionSize; i++) {
          if (mBigramSuggestions.get(i).charAt(0) == currentChar
              || mBigramSuggestions.get(i).charAt(0) == currentCharUpper) {
            int poolSize = mStringPool.size();
            StringBuilder sb =
                poolSize > 0
                    ? (StringBuilder) mStringPool.remove(poolSize - 1)
                    : new StringBuilder(getApproxMaxWordLength());
            sb.setLength(0);
            sb.append(mBigramSuggestions.get(i));
            mSuggestions.add(count++, sb);
            if (count > mPrefMaxSuggestions) break;
          }
        }
      }

    } else if (wordComposer.size() > 1) {
      // At second character typed, search the unigrams (scores being affected by bigrams)
      if (mUserDictionary != null || mContactsDictionary != null) {
        if (mUserDictionary != null) {
          mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
        }
        if (mContactsDictionary != null) {
          mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
        }

        if (mSuggestions.size() > 0
            && isValidWord(mOriginalWord)
            && (mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
          mHaveCorrection = true;
        }
      }
      mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
      if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
          && mSuggestions.size() > 0) {
        mHaveCorrection = true;
      }
    }
    if (mOriginalWord != null) {
      mSuggestions.add(0, mOriginalWord.toString());
    }

    // Check if the first suggestion has a minimum number of characters in common
    if (wordComposer.size() > 1
        && mSuggestions.size() > 1
        && (mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
      if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) {
        mHaveCorrection = false;
      }
    }
    if (mAutoTextEnabled) {
      int i = 0;
      int max = 6;
      // Don't autotext the suggestions from the dictionaries
      if (mCorrectionMode == CORRECTION_BASIC) max = 1;
      while (i < mSuggestions.size() && i < max) {
        String suggestedWord = mSuggestions.get(i).toString().toLowerCase();
        CharSequence autoText = AutoText.get(suggestedWord, 0, suggestedWord.length(), view);
        // Is there an AutoText correction?
        boolean canAdd = autoText != null;
        // Is that correction already the current prediction (or original word)?
        canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i));
        // Is that correction already the next predicted word?
        if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) {
          canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1));
        }
        if (canAdd) {
          mHaveCorrection = true;
          mSuggestions.add(i + 1, autoText);
          i++;
        }
        i++;
      }
    }
    removeDupes();
    return mSuggestions;
  }
Exemplo n.º 6
0
  // Retrieves suggestions for the batch input
  // and calls the callback function with the suggestions.
  private void getSuggestedWordsForBatchInput(
      final WordComposer wordComposer,
      final String prevWordForBigram,
      final ProximityInfo proximityInfo,
      final boolean blockOffensiveWords,
      final int[] additionalFeaturesOptions,
      final int sessionId,
      final int sequenceNumber,
      final OnGetSuggestedWordsCallback callback) {
    final BoundedTreeSet suggestionsSet =
        new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS);

    // At second character typed, search the unigrams (scores being affected by bigrams)
    for (final String key : mDictionaries.keySet()) {
      final Dictionary dictionary = mDictionaries.get(key);
      suggestionsSet.addAll(
          dictionary.getSuggestionsWithSessionId(
              wordComposer,
              prevWordForBigram,
              proximityInfo,
              blockOffensiveWords,
              additionalFeaturesOptions,
              sessionId));
    }

    for (SuggestedWordInfo wordInfo : suggestionsSet) {
      LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
    }

    final ArrayList<SuggestedWordInfo> suggestionsContainer =
        CollectionUtils.newArrayList(suggestionsSet);
    final int suggestionsCount = suggestionsContainer.size();
    final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
    final boolean isAllUpperCase = wordComposer.isAllUpperCase();
    if (isFirstCharCapitalized || isAllUpperCase) {
      for (int i = 0; i < suggestionsCount; ++i) {
        final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
        final SuggestedWordInfo transformedWordInfo =
            getTransformedSuggestedWordInfo(
                wordInfo,
                mLocale,
                isAllUpperCase,
                isFirstCharCapitalized,
                0 /* trailingSingleQuotesCount */);
        suggestionsContainer.set(i, transformedWordInfo);
      }
    }

    if (suggestionsContainer.size() > 1
        && TextUtils.equals(
            suggestionsContainer.get(0).mWord, wordComposer.getRejectedBatchModeSuggestion())) {
      final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
      suggestionsContainer.add(1, rejected);
    }
    SuggestedWordInfo.removeDups(suggestionsContainer);

    // For some reason some suggestions with MIN_VALUE are making their way here.
    // TODO: Find a more robust way to detect distractors.
    for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
      if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
        suggestionsContainer.remove(i);
      }
    }

    // In the batch input mode, the most relevant suggested word should act as a "typed word"
    // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
    callback.onGetSuggestedWords(
        new SuggestedWords(
            suggestionsContainer,
            true /* typedWordValid */,
            false /* willAutoCorrect */,
            false /* isPunctuationSuggestions */,
            false /* isObsoleteSuggestions */,
            false /* isPrediction */,
            sequenceNumber));
  }
Exemplo n.º 7
0
  // Retrieves suggestions for the typing input
  // and calls the callback function with the suggestions.
  private void getSuggestedWordsForTypingInput(
      final WordComposer wordComposer,
      final String prevWordForBigram,
      final ProximityInfo proximityInfo,
      final boolean blockOffensiveWords,
      final boolean isCorrectionEnabled,
      final int[] additionalFeaturesOptions,
      final int sequenceNumber,
      final OnGetSuggestedWordsCallback callback) {
    final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
    final BoundedTreeSet suggestionsSet =
        new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS);

    final String typedWord = wordComposer.getTypedWord();
    final String consideredWord =
        trailingSingleQuotesCount > 0
            ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
            : typedWord;
    LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);

    final WordComposer wordComposerForLookup;
    if (trailingSingleQuotesCount > 0) {
      wordComposerForLookup = new WordComposer(wordComposer);
      for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
        wordComposerForLookup.deleteLast();
      }
    } else {
      wordComposerForLookup = wordComposer;
    }

    for (final String key : mDictionaries.keySet()) {
      final Dictionary dictionary = mDictionaries.get(key);
      suggestionsSet.addAll(
          dictionary.getSuggestions(
              wordComposerForLookup,
              prevWordForBigram,
              proximityInfo,
              blockOffensiveWords,
              additionalFeaturesOptions));
    }

    final String whitelistedWord;
    if (suggestionsSet.isEmpty()) {
      whitelistedWord = null;
    } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
      whitelistedWord = null;
    } else {
      whitelistedWord = suggestionsSet.first().mWord;
    }

    // The word can be auto-corrected if it has a whitelist entry that is not itself,
    // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
    final boolean allowsToBeAutoCorrected =
        (null != whitelistedWord && !whitelistedWord.equals(consideredWord))
            || (consideredWord.length() > 1
                && !AutoCorrectionUtils.isValidWord(
                    this, consideredWord, wordComposer.isFirstCharCapitalized()));

    final boolean hasAutoCorrection;
    // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
    // any attempt to do auto-correction is already shielded with a test for this flag; at the
    // same time, it feels wrong that the SuggestedWord object includes information about
    // the current settings. It may also be useful to know, when the setting is off, whether
    // the word *would* have been auto-corrected.
    if (!isCorrectionEnabled
        || !allowsToBeAutoCorrected
        || !wordComposer.isComposingWord()
        || suggestionsSet.isEmpty()
        || wordComposer.hasDigits()
        || wordComposer.isMostlyCaps()
        || wordComposer.isResumed()
        || !hasMainDictionary()
        || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) {
      // If we don't have a main dictionary, we never want to auto-correct. The reason for
      // this is, the user may have a contact whose name happens to match a valid word in
      // their language, and it will unexpectedly auto-correct. For example, if the user
      // types in English with no dictionary and has a "Will" in their contact list, "will"
      // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
      // auto-correct.
      // Also, shortcuts should never auto-correct unless they are whitelist entries.
      // TODO: we may want to have shortcut-only entries auto-correct in the future.
      hasAutoCorrection = false;
    } else {
      hasAutoCorrection =
          AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
              suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
    }

    final ArrayList<SuggestedWordInfo> suggestionsContainer =
        CollectionUtils.newArrayList(suggestionsSet);
    final int suggestionsCount = suggestionsContainer.size();
    final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
    final boolean isAllUpperCase = wordComposer.isAllUpperCase();
    if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
      for (int i = 0; i < suggestionsCount; ++i) {
        final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
        final SuggestedWordInfo transformedWordInfo =
            getTransformedSuggestedWordInfo(
                wordInfo,
                mLocale,
                isAllUpperCase,
                isFirstCharCapitalized,
                trailingSingleQuotesCount);
        suggestionsContainer.set(i, transformedWordInfo);
      }
    }

    for (int i = 0; i < suggestionsCount; ++i) {
      final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
      LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict.mDictType);
    }

    if (!TextUtils.isEmpty(typedWord)) {
      suggestionsContainer.add(
          0,
          new SuggestedWordInfo(
              typedWord,
              SuggestedWordInfo.MAX_SCORE,
              SuggestedWordInfo.KIND_TYPED,
              Dictionary.DICTIONARY_USER_TYPED,
              SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
              SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
    }
    SuggestedWordInfo.removeDups(suggestionsContainer);

    final ArrayList<SuggestedWordInfo> suggestionsList;
    if (DBG && !suggestionsContainer.isEmpty()) {
      suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
    } else {
      suggestionsList = suggestionsContainer;
    }

    callback.onGetSuggestedWords(
        new SuggestedWords(
            suggestionsList,
            // TODO: this first argument is lying. If this is a whitelisted word which is an
            // actual word, it says typedWordValid = false, which looks wrong. We should either
            // rename the attribute or change the value.
            !allowsToBeAutoCorrected /* typedWordValid */,
            hasAutoCorrection, /* willAutoCorrect */
            false /* isPunctuationSuggestions */,
            false /* isObsoleteSuggestions */,
            !wordComposer.isComposingWord() /* isPrediction */,
            sequenceNumber));
  }