AndroidSpellCheckerService.java revision 3234123fba901243990972158d023a5d1c273316
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.util.Log;
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.Collections;
36import java.util.List;
37import java.util.LinkedList;
38import java.util.Locale;
39import java.util.Map;
40import java.util.TreeMap;
41
42/**
43 * Service for spell checking, using LatinIME's dictionaries and mechanisms.
44 */
45public class AndroidSpellCheckerService extends SpellCheckerService {
46    private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
47    private static final boolean DBG = true;
48
49    private final static String[] emptyArray = new String[0];
50    private final ProximityInfo mProximityInfo = ProximityInfo.getDummyProximityInfo();
51    private final Map<String, Dictionary> mDictionaries =
52            Collections.synchronizedMap(new TreeMap<String, Dictionary>());
53
54    private static class SuggestionsGatherer implements WordCallback {
55        private final int DEFAULT_SUGGESTION_LENGTH = 16;
56        private final String[] mSuggestions;
57        private final int[] mScores;
58        private final int mMaxLength;
59        private int mLength = 0;
60
61        SuggestionsGatherer(final int maxLength) {
62            mMaxLength = maxLength;
63            mSuggestions = new String[mMaxLength];
64            mScores = new int[mMaxLength];
65        }
66
67        @Override
68        synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
69                int dicTypeId, DataType dataType) {
70            final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
71            // binarySearch returns the index if the element exists, and -<insertion index> - 1
72            // if it doesn't. See documentation for binarySearch.
73            final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
74
75            if (mLength < mMaxLength) {
76                final int copyLen = mLength - insertIndex;
77                ++mLength;
78                System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
79                System.arraycopy(mSuggestions, insertIndex, mSuggestions, insertIndex + 1, copyLen);
80            } else {
81                if (insertIndex == 0) return true;
82                System.arraycopy(mScores, 1, mScores, 0, insertIndex);
83                System.arraycopy(mSuggestions, 1, mSuggestions, 0, insertIndex);
84            }
85            mScores[insertIndex] = score;
86            mSuggestions[insertIndex] = new String(word, wordOffset, wordLength);
87
88            return true;
89        }
90
91        public String[] getGatheredSuggestions() {
92            if (0 == mLength) return null;
93
94            final String[] results = new String[mLength];
95            for (int i = mLength - 1; i >= 0; --i) {
96                results[mLength - i - 1] = mSuggestions[i];
97            }
98            return results;
99        }
100    }
101
102    private Dictionary getDictionary(final String locale) {
103        Dictionary dictionary = mDictionaries.get(locale);
104        if (null == dictionary) {
105            final Resources resources = getResources();
106            final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
107            final Locale localeObject = Utils.constructLocaleFromString(locale);
108            dictionary = DictionaryFactory.createDictionaryFromManager(this, localeObject,
109                    fallbackResourceId);
110            mDictionaries.put(locale, dictionary);
111        }
112        return dictionary;
113    }
114
115    // Note : this must be reentrant
116    /**
117     * Gets a list of suggestions for a specific string.
118     *
119     * This returns a list of possible corrections for the text passed as an
120     * arguments. It may split or group words, and even perform grammatical
121     * analysis.
122     */
123    @Override
124    public SuggestionsInfo getSuggestions(final TextInfo textInfo, final int suggestionsLimit,
125            final String locale) {
126        final Dictionary dictionary = getDictionary(locale);
127        final String text = textInfo.getText();
128
129        final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(suggestionsLimit);
130        final WordComposer composer = new WordComposer();
131        final int length = text.length();
132        for (int i = 0; i < length; ++i) {
133            int character = text.codePointAt(i);
134            composer.add(character, new int[] { character },
135                    WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
136        }
137        dictionary.getWords(composer, suggestionsGatherer, mProximityInfo);
138        final boolean isInDict = dictionary.isValidWord(text);
139        final String[] suggestions = suggestionsGatherer.getGatheredSuggestions();
140
141        final int flags = (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
142                | (null != suggestions ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
143        return new SuggestionsInfo(flags, suggestions);
144    }
145}
146