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