AndroidSpellCheckerService.java revision f098fbbef324df034cc04de04d9b5fe6657238c7
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.res.Resources; 20import android.service.textservice.SpellCheckerService; 21import android.service.textservice.SpellCheckerService.Session; 22import android.view.textservice.SuggestionsInfo; 23import android.view.textservice.TextInfo; 24 25import com.android.inputmethod.compat.ArraysCompatUtils; 26import com.android.inputmethod.keyboard.Key; 27import com.android.inputmethod.keyboard.ProximityInfo; 28import com.android.inputmethod.latin.Dictionary; 29import com.android.inputmethod.latin.Dictionary.DataType; 30import com.android.inputmethod.latin.Dictionary.WordCallback; 31import com.android.inputmethod.latin.DictionaryFactory; 32import com.android.inputmethod.latin.Utils; 33import com.android.inputmethod.latin.WordComposer; 34 35import java.util.Arrays; 36import java.util.Collections; 37import java.util.List; 38import java.util.LinkedList; 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 = true; 49 50 private final static String[] emptyArray = new String[0]; 51 private final ProximityInfo mProximityInfo = ProximityInfo.getSpellCheckerProximityInfo(); 52 private final Map<String, Dictionary> mDictionaries = 53 Collections.synchronizedMap(new TreeMap<String, Dictionary>()); 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 private Dictionary getDictionary(final String locale) { 109 Dictionary dictionary = mDictionaries.get(locale); 110 if (null == dictionary) { 111 final Resources resources = getResources(); 112 final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources); 113 final Locale localeObject = Utils.constructLocaleFromString(locale); 114 dictionary = DictionaryFactory.createDictionaryFromManager(this, localeObject, 115 fallbackResourceId); 116 mDictionaries.put(locale, dictionary); 117 } 118 return dictionary; 119 } 120 121 private class AndroidSpellCheckerSession extends Session { 122 @Override 123 public void onCreate() { 124 } 125 126 // Note : this must be reentrant 127 /** 128 * Gets a list of suggestions for a specific string. This returns a list of possible 129 * corrections for the text passed as an arguments. It may split or group words, and 130 * even perform grammatical analysis. 131 */ 132 @Override 133 public SuggestionsInfo onGetSuggestions(final TextInfo textInfo, 134 final int suggestionsLimit) { 135 final String locale = getLocale(); 136 final Dictionary dictionary = getDictionary(locale); 137 final String text = textInfo.getText(); 138 139 final SuggestionsGatherer suggestionsGatherer = 140 new SuggestionsGatherer(suggestionsLimit); 141 final WordComposer composer = new WordComposer(); 142 final int length = text.length(); 143 for (int i = 0; i < length; ++i) { 144 final int character = text.codePointAt(i); 145 final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character); 146 final int[] proximities; 147 if (-1 == proximityIndex) { 148 proximities = new int[] { character }; 149 } else { 150 proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY, 151 proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE); 152 } 153 composer.add(character, proximities, 154 WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); 155 } 156 dictionary.getWords(composer, suggestionsGatherer, mProximityInfo); 157 final boolean isInDict = dictionary.isValidWord(text); 158 final String[] suggestions = suggestionsGatherer.getGatheredSuggestions(); 159 160 final int flags = 161 (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0) 162 | (null != suggestions 163 ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0); 164 return new SuggestionsInfo(flags, suggestions); 165 } 166 } 167} 168