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.personalization; 18 19 import android.content.Context; 20 21 import com.android.inputmethod.annotations.UsedForTesting; 22 import com.android.inputmethod.compat.ActivityManagerCompatUtils; 23 import com.android.inputmethod.keyboard.ProximityInfo; 24 import com.android.inputmethod.latin.AbstractDictionaryWriter; 25 import com.android.inputmethod.latin.ExpandableDictionary; 26 import com.android.inputmethod.latin.WordComposer; 27 import com.android.inputmethod.latin.ExpandableDictionary.NextWord; 28 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 29 import com.android.inputmethod.latin.makedict.DictEncoder; 30 import com.android.inputmethod.latin.makedict.FormatSpec; 31 import com.android.inputmethod.latin.makedict.UnsupportedFormatException; 32 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils; 33 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface; 34 import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils; 35 import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams; 36 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.Map; 40 41 // Currently this class is used to implement dynamic prodiction dictionary. 42 // TODO: Move to native code. 43 public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWriter { 44 private static final String TAG = DynamicPersonalizationDictionaryWriter.class.getSimpleName(); 45 /** Maximum number of pairs. Pruning will start when databases goes above this number. */ 46 public static final int DEFAULT_MAX_HISTORY_BIGRAMS = 10000; 47 public static final int LOW_MEMORY_MAX_HISTORY_BIGRAMS = 2000; 48 49 /** Any pair being typed or picked */ 50 private static final int FREQUENCY_FOR_TYPED = 2; 51 52 private static final int BINARY_DICT_VERSION = 3; 53 private static final FormatSpec.FormatOptions FORMAT_OPTIONS = 54 new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */); 55 56 private final UserHistoryDictionaryBigramList mBigramList = 57 new UserHistoryDictionaryBigramList(); 58 private final ExpandableDictionary mExpandableDictionary; 59 private final int mMaxHistoryBigrams; 60 61 public DynamicPersonalizationDictionaryWriter(final Context context, final String dictType) { 62 super(context, dictType); 63 mExpandableDictionary = new ExpandableDictionary(dictType); 64 final boolean isLowRamDevice = ActivityManagerCompatUtils.isLowRamDevice(context); 65 mMaxHistoryBigrams = isLowRamDevice ? 66 LOW_MEMORY_MAX_HISTORY_BIGRAMS : DEFAULT_MAX_HISTORY_BIGRAMS; 67 } 68 69 @Override 70 public void clear() { 71 mBigramList.evictAll(); 72 mExpandableDictionary.clearDictionary(); 73 } 74 75 /** 76 * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes 77 * are done to update the binary dictionary. 78 * @param word The word to add. 79 * @param shortcutTarget A shortcut target for this word, or null if none. 80 * @param frequency The frequency for this unigram. 81 * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored 82 * if shortcutTarget is null. 83 * @param isNotAWord true if this is not a word, i.e. shortcut only. 84 */ 85 @Override 86 public void addUnigramWord(final String word, final String shortcutTarget, final int frequency, 87 final int shortcutFreq, final boolean isNotAWord) { 88 if (mBigramList.size() > mMaxHistoryBigrams * 2) { 89 // Too many entries: just stop adding new vocabulary and wait next refresh. 90 return; 91 } 92 mExpandableDictionary.addWord(word, shortcutTarget, frequency, shortcutFreq); 93 mBigramList.addBigram(null, word, (byte)frequency); 94 } 95 96 @Override 97 public void addBigramWords(final String word0, final String word1, final int frequency, 98 final boolean isValid, final long lastModifiedTime) { 99 if (mBigramList.size() > mMaxHistoryBigrams * 2) { 100 // Too many entries: just stop adding new vocabulary and wait next refresh. 101 return; 102 } 103 if (lastModifiedTime > 0) { 104 mExpandableDictionary.setBigramAndGetFrequency(word0, word1, 105 new ForgettingCurveParams(frequency, System.currentTimeMillis(), 106 lastModifiedTime)); 107 mBigramList.addBigram(word0, word1, (byte)frequency); 108 } else { 109 mExpandableDictionary.setBigramAndGetFrequency(word0, word1, 110 new ForgettingCurveParams(isValid)); 111 mBigramList.addBigram(word0, word1, (byte)frequency); 112 } 113 } 114 115 @Override 116 public void removeBigramWords(final String word0, final String word1) { 117 if (mBigramList.removeBigram(word0, word1)) { 118 mExpandableDictionary.removeBigram(word0, word1); 119 } 120 } 121 122 @Override 123 protected void writeDictionary(final DictEncoder dictEncoder, 124 final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException { 125 UserHistoryDictIOUtils.writeDictionary(dictEncoder, 126 new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams), 127 mBigramList, FORMAT_OPTIONS); 128 } 129 130 private static class FrequencyProvider implements BigramDictionaryInterface { 131 private final UserHistoryDictionaryBigramList mBigramList; 132 private final ExpandableDictionary mExpandableDictionary; 133 private final int mMaxHistoryBigrams; 134 135 public FrequencyProvider(final UserHistoryDictionaryBigramList bigramList, 136 final ExpandableDictionary expandableDictionary, final int maxHistoryBigrams) { 137 mBigramList = bigramList; 138 mExpandableDictionary = expandableDictionary; 139 mMaxHistoryBigrams = maxHistoryBigrams; 140 } 141 142 @Override 143 public int getFrequency(final String word0, final String word1) { 144 final int freq; 145 if (word0 == null) { // unigram 146 freq = FREQUENCY_FOR_TYPED; 147 } else { // bigram 148 final NextWord nw = mExpandableDictionary.getBigramWord(word0, word1); 149 if (nw != null) { 150 final ForgettingCurveParams forgettingCurveParams = nw.getFcParams(); 151 final byte prevFc = mBigramList.getBigrams(word0).get(word1); 152 final byte fc = forgettingCurveParams.getFc(); 153 final boolean isValid = forgettingCurveParams.isValid(); 154 if (prevFc > 0 && prevFc == fc) { 155 freq = fc & 0xFF; 156 } else if (UserHistoryForgettingCurveUtils. 157 needsToSave(fc, isValid, mBigramList.size() <= mMaxHistoryBigrams)) { 158 freq = fc & 0xFF; 159 } else { 160 // Delete this entry 161 freq = -1; 162 } 163 } else { 164 // Delete this entry 165 freq = -1; 166 } 167 } 168 return freq; 169 } 170 } 171 172 @Override 173 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, 174 final String prevWord, final ProximityInfo proximityInfo, 175 boolean blockOffensiveWords, final int[] additionalFeaturesOptions) { 176 return mExpandableDictionary.getSuggestions(composer, prevWord, proximityInfo, 177 blockOffensiveWords, additionalFeaturesOptions); 178 } 179 180 @Override 181 public boolean isValidWord(final String word) { 182 return mExpandableDictionary.isValidWord(word); 183 } 184 185 @UsedForTesting 186 public boolean isInBigramListForTests(final String word) { 187 // TODO: Use native method to determine whether the word is in dictionary or not 188 return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word); 189 } 190 } 191