AndroidSpellCheckerService.java revision c160373b6a8e8a536ad8aa2798a33a41d3050f3b
1/* 2 * Copyright (C) 2011 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 17package com.android.inputmethod.latin.spellcheck; 18 19import android.content.Intent; 20import android.content.res.Resources; 21import android.service.textservice.SpellCheckerService; 22import android.service.textservice.SpellCheckerService.Session; 23import android.util.Log; 24import android.view.textservice.SuggestionsInfo; 25import android.view.textservice.TextInfo; 26 27import com.android.inputmethod.compat.ArraysCompatUtils; 28import com.android.inputmethod.keyboard.Key; 29import com.android.inputmethod.keyboard.ProximityInfo; 30import com.android.inputmethod.latin.Dictionary; 31import com.android.inputmethod.latin.Dictionary.DataType; 32import com.android.inputmethod.latin.Dictionary.WordCallback; 33import com.android.inputmethod.latin.DictionaryFactory; 34import com.android.inputmethod.latin.Utils; 35import com.android.inputmethod.latin.WordComposer; 36 37import java.util.Arrays; 38import java.util.Collections; 39import java.util.Locale; 40import java.util.Map; 41import java.util.TreeMap; 42 43/** 44 * Service for spell checking, using LatinIME's dictionaries and mechanisms. 45 */ 46public class AndroidSpellCheckerService extends SpellCheckerService { 47 private static final String TAG = AndroidSpellCheckerService.class.getSimpleName(); 48 private static final boolean DBG = false; 49 private static final int POOL_SIZE = 2; 50 51 private final static String[] emptyArray = new String[0]; 52 private Map<String, DictionaryPool> mDictionaryPools = 53 Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); 54 55 @Override 56 public Session createSession() { 57 return new AndroidSpellCheckerSession(); 58 } 59 60 private static class SuggestionsGatherer implements WordCallback { 61 private final int DEFAULT_SUGGESTION_LENGTH = 16; 62 private final String[] mSuggestions; 63 private final int[] mScores; 64 private final int mMaxLength; 65 private int mLength = 0; 66 67 SuggestionsGatherer(final int maxLength) { 68 mMaxLength = maxLength; 69 mSuggestions = new String[mMaxLength]; 70 mScores = new int[mMaxLength]; 71 } 72 73 @Override 74 synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score, 75 int dicTypeId, DataType dataType) { 76 final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score); 77 // binarySearch returns the index if the element exists, and -<insertion index> - 1 78 // if it doesn't. See documentation for binarySearch. 79 final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1; 80 81 if (mLength < mMaxLength) { 82 final int copyLen = mLength - insertIndex; 83 ++mLength; 84 System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen); 85 System.arraycopy(mSuggestions, insertIndex, mSuggestions, insertIndex + 1, copyLen); 86 } else { 87 if (insertIndex == 0) return true; 88 System.arraycopy(mScores, 1, mScores, 0, insertIndex); 89 System.arraycopy(mSuggestions, 1, mSuggestions, 0, insertIndex); 90 } 91 mScores[insertIndex] = score; 92 mSuggestions[insertIndex] = new String(word, wordOffset, wordLength); 93 94 return true; 95 } 96 97 public String[] getGatheredSuggestions() { 98 if (0 == mLength) return null; 99 100 final String[] results = new String[mLength]; 101 for (int i = mLength - 1; i >= 0; --i) { 102 results[mLength - i - 1] = mSuggestions[i]; 103 } 104 return results; 105 } 106 } 107 108 @Override 109 public boolean onUnbind(final Intent intent) { 110 final Map<String, DictionaryPool> oldPools = mDictionaryPools; 111 mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); 112 for (DictionaryPool pool : oldPools.values()) { 113 pool.close(); 114 } 115 return false; 116 } 117 118 private DictionaryPool getDictionaryPool(final String locale) { 119 DictionaryPool pool = mDictionaryPools.get(locale); 120 if (null == pool) { 121 final Locale localeObject = Utils.constructLocaleFromString(locale); 122 pool = new DictionaryPool(POOL_SIZE, this, localeObject); 123 mDictionaryPools.put(locale, pool); 124 } 125 return pool; 126 } 127 128 public DictAndProximity createDictAndProximity(final Locale locale) { 129 final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(); 130 final Resources resources = getResources(); 131 final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources); 132 final Dictionary dictionary = 133 DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId); 134 return new DictAndProximity(dictionary, proximityInfo); 135 } 136 137 private class AndroidSpellCheckerSession extends Session { 138 // Immutable, but need the locale which is not available in the constructor yet 139 DictionaryPool mDictionaryPool; 140 141 @Override 142 public void onCreate() { 143 mDictionaryPool = getDictionaryPool(getLocale()); 144 } 145 146 // Note : this must be reentrant 147 /** 148 * Gets a list of suggestions for a specific string. This returns a list of possible 149 * corrections for the text passed as an argument. It may split or group words, and 150 * even perform grammatical analysis. 151 */ 152 @Override 153 public SuggestionsInfo onGetSuggestions(final TextInfo textInfo, 154 final int suggestionsLimit) { 155 final String text = textInfo.getText(); 156 157 final SuggestionsGatherer suggestionsGatherer = 158 new SuggestionsGatherer(suggestionsLimit); 159 final WordComposer composer = new WordComposer(); 160 final int length = text.length(); 161 for (int i = 0; i < length; ++i) { 162 final int character = text.codePointAt(i); 163 final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character); 164 final int[] proximities; 165 if (-1 == proximityIndex) { 166 proximities = new int[] { character }; 167 } else { 168 proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY, 169 proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE); 170 } 171 composer.add(character, proximities, 172 WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); 173 } 174 175 boolean isInDict = true; 176 try { 177 final DictAndProximity dictInfo = mDictionaryPool.take(); 178 dictInfo.mDictionary.getWords(composer, suggestionsGatherer, 179 dictInfo.mProximityInfo); 180 isInDict = dictInfo.mDictionary.isValidWord(text); 181 if (!mDictionaryPool.offer(dictInfo)) { 182 Log.e(TAG, "Can't re-insert a dictionary into its pool"); 183 } 184 } catch (InterruptedException e) { 185 // I don't think this can happen. 186 return new SuggestionsInfo(0, new String[0]); 187 } 188 189 final String[] suggestions = suggestionsGatherer.getGatheredSuggestions(); 190 191 final int flags = 192 (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0) 193 | (null != suggestions 194 ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0); 195 return new SuggestionsInfo(flags, suggestions); 196 } 197 } 198} 199