2626import android .os .IBinder ;
2727import android .os .Process ;
2828import android .os .RemoteException ;
29+ import android .text .TextUtils ;
30+ import android .text .method .WordIterator ;
2931import android .util .Log ;
3032import android .view .textservice .SentenceSuggestionsInfo ;
3133import android .view .textservice .SuggestionsInfo ;
3234import android .view .textservice .TextInfo ;
35+ import android .widget .SpellChecker ;
3336
3437import java .lang .ref .WeakReference ;
38+ import java .text .BreakIterator ;
39+ import java .util .ArrayList ;
40+ import java .util .Locale ;
3541
3642/**
3743 * SpellCheckerService provides an abstract base class for a spell checker.
@@ -92,6 +98,7 @@ public final IBinder onBind(final Intent intent) {
9298 */
9399 public static abstract class Session {
94100 private InternalISpellCheckerSession mInternalSession ;
101+ private volatile SentenceLevelAdapter mSentenceLevelAdapter ;
95102
96103 /**
97104 * @hide
@@ -142,8 +149,8 @@ public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
142149
143150 /**
144151 * Get sentence suggestions for specified texts in an array of TextInfo.
145- * The default implementation returns an array of SentenceSuggestionsInfo by simply
146- * calling onGetSuggestions .
152+ * The default implementation splits the input text to words and returns
153+ * {@link SentenceSuggestionsInfo} which contains suggestions for each word .
147154 * This function will run on the incoming IPC thread.
148155 * So, this is not called on the main thread,
149156 * but will be called in series on another thread.
@@ -156,14 +163,41 @@ public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
156163 */
157164 public SentenceSuggestionsInfo [] onGetSentenceSuggestionsMultiple (TextInfo [] textInfos ,
158165 int suggestionsLimit ) {
159- final int length = textInfos .length ;
160- final SentenceSuggestionsInfo [] retval = new SentenceSuggestionsInfo [length ];
161- for (int i = 0 ; i < length ; ++i ) {
162- final SuggestionsInfo si = onGetSuggestions (textInfos [i ], suggestionsLimit );
163- si .setCookieAndSequence (textInfos [i ].getCookie (), textInfos [i ].getSequence ());
164- final int N = textInfos [i ].getText ().length ();
165- retval [i ] = new SentenceSuggestionsInfo (
166- new SuggestionsInfo [] {si }, new int []{0 }, new int []{N });
166+ if (textInfos == null || textInfos .length == 0 ) {
167+ return SentenceLevelAdapter .EMPTY_SENTENCE_SUGGESTIONS_INFOS ;
168+ }
169+ if (DBG ) {
170+ Log .d (TAG , "onGetSentenceSuggestionsMultiple: + " + textInfos .length + ", "
171+ + suggestionsLimit );
172+ }
173+ if (mSentenceLevelAdapter == null ) {
174+ synchronized (this ) {
175+ if (mSentenceLevelAdapter == null ) {
176+ final String localeStr = getLocale ();
177+ if (!TextUtils .isEmpty (localeStr )) {
178+ mSentenceLevelAdapter = new SentenceLevelAdapter (new Locale (localeStr ));
179+ }
180+ }
181+ }
182+ }
183+ if (mSentenceLevelAdapter == null ) {
184+ return SentenceLevelAdapter .EMPTY_SENTENCE_SUGGESTIONS_INFOS ;
185+ }
186+ final int infosSize = textInfos .length ;
187+ final SentenceSuggestionsInfo [] retval = new SentenceSuggestionsInfo [infosSize ];
188+ for (int i = 0 ; i < infosSize ; ++i ) {
189+ final SentenceLevelAdapter .SentenceTextInfoParams textInfoParams =
190+ mSentenceLevelAdapter .getSplitWords (textInfos [i ]);
191+ final ArrayList <SentenceLevelAdapter .SentenceWordItem > mItems =
192+ textInfoParams .mItems ;
193+ final int itemsSize = mItems .size ();
194+ final TextInfo [] splitTextInfos = new TextInfo [itemsSize ];
195+ for (int j = 0 ; j < itemsSize ; ++j ) {
196+ splitTextInfos [j ] = mItems .get (j ).mTextInfo ;
197+ }
198+ retval [i ] = SentenceLevelAdapter .reconstructSuggestions (
199+ textInfoParams , onGetSuggestionsMultiple (
200+ splitTextInfos , suggestionsLimit , true ));
167201 }
168202 return retval ;
169203 }
@@ -290,4 +324,135 @@ public ISpellCheckerSession getISpellCheckerSession(
290324 return internalSession ;
291325 }
292326 }
327+
328+ /**
329+ * Adapter class to accommodate word level spell checking APIs to sentence level spell checking
330+ * APIs used in
331+ * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)}
332+ */
333+ private static class SentenceLevelAdapter {
334+ public static final SentenceSuggestionsInfo [] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
335+ new SentenceSuggestionsInfo [] {};
336+ private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo (0 , null );
337+ /**
338+ * Container for split TextInfo parameters
339+ */
340+ public static class SentenceWordItem {
341+ public final TextInfo mTextInfo ;
342+ public final int mStart ;
343+ public final int mLength ;
344+ public SentenceWordItem (TextInfo ti , int start , int end ) {
345+ mTextInfo = ti ;
346+ mStart = start ;
347+ mLength = end - start ;
348+ }
349+ }
350+
351+ /**
352+ * Container for originally queried TextInfo and parameters
353+ */
354+ public static class SentenceTextInfoParams {
355+ final TextInfo mOriginalTextInfo ;
356+ final ArrayList <SentenceWordItem > mItems ;
357+ final int mSize ;
358+ public SentenceTextInfoParams (TextInfo ti , ArrayList <SentenceWordItem > items ) {
359+ mOriginalTextInfo = ti ;
360+ mItems = items ;
361+ mSize = items .size ();
362+ }
363+ }
364+
365+ private final WordIterator mWordIterator ;
366+ public SentenceLevelAdapter (Locale locale ) {
367+ mWordIterator = new WordIterator (locale );
368+ }
369+
370+ private SentenceTextInfoParams getSplitWords (TextInfo originalTextInfo ) {
371+ final WordIterator wordIterator = mWordIterator ;
372+ final CharSequence originalText = originalTextInfo .getText ();
373+ final int cookie = originalTextInfo .getCookie ();
374+ final int start = 0 ;
375+ final int end = originalText .length ();
376+ final ArrayList <SentenceWordItem > wordItems = new ArrayList <SentenceWordItem >();
377+ wordIterator .setCharSequence (originalText , 0 , originalText .length ());
378+ int wordEnd = wordIterator .following (start );
379+ int wordStart = wordIterator .getBeginning (wordEnd );
380+ if (DBG ) {
381+ Log .d (TAG , "iterator: break: ---- 1st word start = " + wordStart + ", end = "
382+ + wordEnd + "\n " + originalText );
383+ }
384+ while (wordStart <= end && wordEnd != BreakIterator .DONE
385+ && wordStart != BreakIterator .DONE ) {
386+ if (wordEnd >= start && wordEnd > wordStart ) {
387+ final String query = originalText .subSequence (wordStart , wordEnd ).toString ();
388+ final TextInfo ti = new TextInfo (query , cookie , query .hashCode ());
389+ wordItems .add (new SentenceWordItem (ti , wordStart , wordEnd ));
390+ if (DBG ) {
391+ Log .d (TAG , "Adapter: word (" + (wordItems .size () - 1 ) + ") " + query );
392+ }
393+ }
394+ wordEnd = wordIterator .following (wordEnd );
395+ if (wordEnd == BreakIterator .DONE ) {
396+ break ;
397+ }
398+ wordStart = wordIterator .getBeginning (wordEnd );
399+ }
400+ if (originalText .length () >= SpellChecker .WORD_ITERATOR_INTERVAL
401+ && wordItems .size () >= 2 ) {
402+ if (DBG ) {
403+ Log .w (TAG , "Remove possibly divided word: "
404+ + wordItems .get (0 ).mTextInfo .getText ());
405+ }
406+ wordItems .remove (0 );
407+ }
408+ return new SentenceTextInfoParams (originalTextInfo , wordItems );
409+ }
410+
411+ public static SentenceSuggestionsInfo reconstructSuggestions (
412+ SentenceTextInfoParams originalTextInfoParams , SuggestionsInfo [] results ) {
413+ if (results == null || results .length == 0 ) {
414+ return null ;
415+ }
416+ if (DBG ) {
417+ Log .w (TAG , "Adapter: onGetSuggestions: got " + results .length );
418+ }
419+ if (originalTextInfoParams == null ) {
420+ if (DBG ) {
421+ Log .w (TAG , "Adapter: originalTextInfoParams is null." );
422+ }
423+ return null ;
424+ }
425+ final int originalCookie = originalTextInfoParams .mOriginalTextInfo .getCookie ();
426+ final int originalSequence =
427+ originalTextInfoParams .mOriginalTextInfo .getSequence ();
428+
429+ final int querySize = originalTextInfoParams .mSize ;
430+ final int [] offsets = new int [querySize ];
431+ final int [] lengths = new int [querySize ];
432+ final SuggestionsInfo [] reconstructedSuggestions = new SuggestionsInfo [querySize ];
433+ for (int i = 0 ; i < querySize ; ++i ) {
434+ final SentenceWordItem item = originalTextInfoParams .mItems .get (i );
435+ SuggestionsInfo result = null ;
436+ for (int j = 0 ; j < results .length ; ++j ) {
437+ final SuggestionsInfo cur = results [j ];
438+ if (cur != null && cur .getSequence () == item .mTextInfo .getSequence ()) {
439+ result = cur ;
440+ result .setCookieAndSequence (originalCookie , originalSequence );
441+ break ;
442+ }
443+ }
444+ offsets [i ] = item .mStart ;
445+ lengths [i ] = item .mLength ;
446+ reconstructedSuggestions [i ] = result != null ? result : EMPTY_SUGGESTIONS_INFO ;
447+ if (DBG ) {
448+ final int size = reconstructedSuggestions [i ].getSuggestionsCount ();
449+ Log .w (TAG , "reconstructedSuggestions(" + i + ")" + size + ", first = "
450+ + (size > 0 ? reconstructedSuggestions [i ].getSuggestionAt (0 )
451+ : "<none>" ) + ", offset = " + offsets [i ] + ", length = "
452+ + lengths [i ]);
453+ }
454+ }
455+ return new SentenceSuggestionsInfo (reconstructedSuggestions , offsets , lengths );
456+ }
457+ }
293458}
0 commit comments