2222import android .text .Spanned ;
2323import android .text .style .SpellCheckSpan ;
2424import android .text .style .SuggestionSpan ;
25- import android .util .Log ;
2625import android .view .textservice .SpellCheckerSession ;
2726import android .view .textservice .SpellCheckerSession .SpellCheckerSessionListener ;
2827import android .view .textservice .SuggestionsInfo ;
4039 * @hide
4140 */
4241public class SpellChecker implements SpellCheckerSessionListener {
43- private static final String LOG_TAG = "SpellChecker" ;
44- private static final boolean DEBUG_SPELL_CHECK = false ;
45- private static final int DELAY_BEFORE_SPELL_CHECK = 400 ; // milliseconds
4642
4743 private final TextView mTextView ;
4844
4945 final SpellCheckerSession mSpellCheckerSession ;
5046 final int mCookie ;
5147
52- // Paired arrays for the (id, spellCheckSpan) pair. mIndex is the next available position
48+ // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
49+ // SpellCheckSpan has been recycled and can be-reused.
50+ // May contain null SpellCheckSpans after a given index.
5351 private int [] mIds ;
5452 private SpellCheckSpan [] mSpellCheckSpans ;
55- // The actual current number of used slots in the above arrays
53+ // The mLength first elements of the above arrays have been initialized
5654 private int mLength ;
5755
5856 private int mSpanSequenceCounter = 0 ;
59- private Runnable mChecker ;
6057
6158 public SpellChecker (TextView textView ) {
6259 mTextView = textView ;
@@ -69,7 +66,7 @@ public SpellChecker(TextView textView) {
6966 mCookie = hashCode ();
7067
7168 // Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand
72- final int size = ArrayUtils .idealObjectArraySize (4 );
69+ final int size = ArrayUtils .idealObjectArraySize (1 );
7370 mIds = new int [size ];
7471 mSpellCheckSpans = new SpellCheckSpan [size ];
7572 mLength = 0 ;
@@ -89,73 +86,50 @@ public void closeSession() {
8986 }
9087 }
9188
92- public void addSpellCheckSpan (SpellCheckSpan spellCheckSpan ) {
93- int length = mIds .length ;
94- if (mLength >= length ) {
95- final int newSize = length * 2 ;
89+ private int nextSpellCheckSpanIndex () {
90+ for (int i = 0 ; i < mLength ; i ++) {
91+ if (mIds [i ] < 0 ) return i ;
92+ }
93+
94+ if (mLength == mSpellCheckSpans .length ) {
95+ final int newSize = mLength * 2 ;
9696 int [] newIds = new int [newSize ];
9797 SpellCheckSpan [] newSpellCheckSpans = new SpellCheckSpan [newSize ];
98- System .arraycopy (mIds , 0 , newIds , 0 , length );
99- System .arraycopy (mSpellCheckSpans , 0 , newSpellCheckSpans , 0 , length );
98+ System .arraycopy (mIds , 0 , newIds , 0 , mLength );
99+ System .arraycopy (mSpellCheckSpans , 0 , newSpellCheckSpans , 0 , mLength );
100100 mIds = newIds ;
101101 mSpellCheckSpans = newSpellCheckSpans ;
102102 }
103103
104- mIds [mLength ] = mSpanSequenceCounter ++;
105- mSpellCheckSpans [mLength ] = spellCheckSpan ;
104+ mSpellCheckSpans [mLength ] = new SpellCheckSpan ();
106105 mLength ++;
106+ return mLength - 1 ;
107+ }
107108
108- if (DEBUG_SPELL_CHECK ) {
109- final Editable mText = (Editable ) mTextView .getText ();
110- int start = mText .getSpanStart (spellCheckSpan );
111- int end = mText .getSpanEnd (spellCheckSpan );
112- if (start >= 0 && end >= 0 ) {
113- Log .d (LOG_TAG , "Schedule check " + mText .subSequence (start , end ));
114- } else {
115- Log .d (LOG_TAG , "Schedule check EMPTY!" );
116- }
117- }
118-
119- scheduleSpellCheck ();
109+ public void addSpellCheckSpan (int wordStart , int wordEnd ) {
110+ final int index = nextSpellCheckSpanIndex ();
111+ ((Editable ) mTextView .getText ()).setSpan (mSpellCheckSpans [index ], wordStart , wordEnd ,
112+ Spanned .SPAN_EXCLUSIVE_EXCLUSIVE );
113+ mIds [index ] = mSpanSequenceCounter ++;
120114 }
121115
122116 public void removeSpellCheckSpan (SpellCheckSpan spellCheckSpan ) {
123117 for (int i = 0 ; i < mLength ; i ++) {
124118 if (mSpellCheckSpans [i ] == spellCheckSpan ) {
125- removeAtIndex (i );
119+ mSpellCheckSpans [i ].setSpellCheckInProgress (false );
120+ mIds [i ] = -1 ;
126121 return ;
127122 }
128123 }
129124 }
130125
131- private void removeAtIndex (int i ) {
132- System .arraycopy (mIds , i + 1 , mIds , i , mLength - i - 1 );
133- System .arraycopy (mSpellCheckSpans , i + 1 , mSpellCheckSpans , i , mLength - i - 1 );
134- mLength --;
135- }
136-
137126 public void onSelectionChanged () {
138- scheduleSpellCheck ();
127+ spellCheck ();
139128 }
140129
141- private void scheduleSpellCheck () {
142- if (mLength == 0 ) return ;
130+ public void spellCheck () {
143131 if (mSpellCheckerSession == null ) return ;
144132
145- if (mChecker != null ) {
146- mTextView .removeCallbacks (mChecker );
147- }
148- if (mChecker == null ) {
149- mChecker = new Runnable () {
150- public void run () {
151- spellCheck ();
152- }
153- };
154- }
155- mTextView .postDelayed (mChecker , DELAY_BEFORE_SPELL_CHECK );
156- }
157-
158- private void spellCheck () {
159133 final Editable editable = (Editable ) mTextView .getText ();
160134 final int selectionStart = Selection .getSelectionStart (editable );
161135 final int selectionEnd = Selection .getSelectionEnd (editable );
@@ -164,8 +138,7 @@ private void spellCheck() {
164138 int textInfosCount = 0 ;
165139
166140 for (int i = 0 ; i < mLength ; i ++) {
167- SpellCheckSpan spellCheckSpan = mSpellCheckSpans [i ];
168-
141+ final SpellCheckSpan spellCheckSpan = mSpellCheckSpans [i ];
169142 if (spellCheckSpan .isSpellCheckInProgress ()) continue ;
170143
171144 final int start = editable .getSpanStart (spellCheckSpan );
@@ -174,7 +147,7 @@ private void spellCheck() {
174147 // Do not check this word if the user is currently editing it
175148 if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end )) {
176149 final String word = editable .subSequence (start , end ).toString ();
177- spellCheckSpan .setSpellCheckInProgress ();
150+ spellCheckSpan .setSpellCheckInProgress (true );
178151 textInfos [textInfosCount ++] = new TextInfo (word , mCookie , mIds [i ]);
179152 }
180153 }
@@ -196,27 +169,18 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
196169 for (int i = 0 ; i < results .length ; i ++) {
197170 SuggestionsInfo suggestionsInfo = results [i ];
198171 if (suggestionsInfo .getCookie () != mCookie ) continue ;
199-
200172 final int sequenceNumber = suggestionsInfo .getSequence ();
201- // Starting from the end, to limit the number of array copy while removing
202- for (int j = mLength - 1 ; j >= 0 ; j --) {
173+
174+ for (int j = 0 ; j < mLength ; j ++) {
175+ final SpellCheckSpan spellCheckSpan = mSpellCheckSpans [j ];
176+
203177 if (sequenceNumber == mIds [j ]) {
204- SpellCheckSpan spellCheckSpan = mSpellCheckSpans [j ];
205178 final int attributes = suggestionsInfo .getSuggestionsAttributes ();
206179 boolean isInDictionary =
207180 ((attributes & SuggestionsInfo .RESULT_ATTR_IN_THE_DICTIONARY ) > 0 );
208181 boolean looksLikeTypo =
209182 ((attributes & SuggestionsInfo .RESULT_ATTR_LOOKS_LIKE_TYPO ) > 0 );
210183
211- if (DEBUG_SPELL_CHECK ) {
212- final int start = editable .getSpanStart (spellCheckSpan );
213- final int end = editable .getSpanEnd (spellCheckSpan );
214- Log .d (LOG_TAG , "Result sequence=" + suggestionsInfo .getSequence () + " " +
215- editable .subSequence (start , end ) +
216- "\t " + (isInDictionary ?"IN_DICT" : "NOT_DICT" ) +
217- "\t " + (looksLikeTypo ?"TYPO" : "NOT_TYPO" ));
218- }
219-
220184 if (!isInDictionary && looksLikeTypo ) {
221185 String [] suggestions = getSuggestions (suggestionsInfo );
222186 if (suggestions .length > 0 ) {
@@ -230,13 +194,6 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
230194 Spanned .SPAN_EXCLUSIVE_EXCLUSIVE );
231195 // TODO limit to the word rectangle region
232196 mTextView .invalidate ();
233-
234- if (DEBUG_SPELL_CHECK ) {
235- String suggestionsString = "" ;
236- for (String s : suggestions ) { suggestionsString += s + "|" ; }
237- Log .d (LOG_TAG , " Suggestions for " + sequenceNumber + " " +
238- editable .subSequence (start , end )+ " " + suggestionsString );
239- }
240197 }
241198 }
242199 editable .removeSpan (spellCheckSpan );
@@ -246,9 +203,10 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
246203 }
247204
248205 private static String [] getSuggestions (SuggestionsInfo suggestionsInfo ) {
206+ // A negative suggestion count is possible
249207 final int len = Math .max (0 , suggestionsInfo .getSuggestionsCount ());
250208 String [] suggestions = new String [len ];
251- for (int j = 0 ; j < len ; ++ j ) {
209+ for (int j = 0 ; j < len ; j ++ ) {
252210 suggestions [j ] = suggestionsInfo .getSuggestionAt (j );
253211 }
254212 return suggestions ;
0 commit comments