OSDN Git Service

Get rid of "isSentenceLevelSpellCheckSupported"
authorsatok <satok@google.com>
Wed, 11 Apr 2012 11:40:07 +0000 (20:40 +0900)
committersatok <satok@google.com>
Mon, 16 Apr 2012 11:02:37 +0000 (20:02 +0900)
Bug: 6320351
Change-Id: I0e931b7248f8c65268b60af599c07432d58d2f1f

api/current.txt
core/java/android/service/textservice/SpellCheckerService.java
core/java/android/view/textservice/SpellCheckerInfo.java
core/java/android/view/textservice/SpellCheckerSession.java
core/java/android/widget/SpellChecker.java
core/res/res/values/attrs.xml
core/res/res/values/public.xml

index 2f8dcb1..2403fff 100644 (file)
@@ -534,7 +534,7 @@ package android {
     field public static final int imeSubtypeLocale = 16843500; // 0x10102ec
     field public static final int imeSubtypeMode = 16843501; // 0x10102ed
     field public static final int immersive = 16843456; // 0x10102c0
-    field public static final int importantForAccessibility = 16843699; // 0x10103b3
+    field public static final int importantForAccessibility = 16843698; // 0x10103b2
     field public static final int inAnimation = 16843127; // 0x1010177
     field public static final int includeFontPadding = 16843103; // 0x101015f
     field public static final int includeInGlobalSearch = 16843374; // 0x101026e
@@ -936,7 +936,6 @@ package android {
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportsRtl = 16843688; // 0x10103a8
-    field public static final int supportsSentenceSpellCheck = 16843698; // 0x10103b2
     field public static final int supportsUploading = 16843419; // 0x101029b
     field public static final int switchMinWidth = 16843632; // 0x1010370
     field public static final int switchPadding = 16843633; // 0x1010371
@@ -25425,9 +25424,8 @@ package android.view.textservice {
     method public void close();
     method public void getSentenceSuggestions(android.view.textservice.TextInfo[], int);
     method public android.view.textservice.SpellCheckerInfo getSpellChecker();
-    method public void getSuggestions(android.view.textservice.TextInfo, int);
-    method public void getSuggestions(android.view.textservice.TextInfo[], int, boolean);
-    method public boolean isSentenceSpellCheckSupported();
+    method public deprecated void getSuggestions(android.view.textservice.TextInfo, int);
+    method public deprecated void getSuggestions(android.view.textservice.TextInfo[], int, boolean);
     method public boolean isSessionDisconnected();
     field public static final java.lang.String SERVICE_META_DATA = "android.view.textservice.scs";
   }
index 53ce32d..c579e6e 100644 (file)
@@ -26,12 +26,18 @@ import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
+import android.text.method.WordIterator;
 import android.util.Log;
 import android.view.textservice.SentenceSuggestionsInfo;
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
+import android.widget.SpellChecker;
 
 import java.lang.ref.WeakReference;
+import java.text.BreakIterator;
+import java.util.ArrayList;
+import java.util.Locale;
 
 /**
  * SpellCheckerService provides an abstract base class for a spell checker.
@@ -92,6 +98,7 @@ public abstract class SpellCheckerService extends Service {
      */
     public static abstract class Session {
         private InternalISpellCheckerSession mInternalSession;
+        private volatile SentenceLevelAdapter mSentenceLevelAdapter;
 
         /**
          * @hide
@@ -142,8 +149,8 @@ public abstract class SpellCheckerService extends Service {
 
         /**
          * Get sentence suggestions for specified texts in an array of TextInfo.
-         * The default implementation returns an array of SentenceSuggestionsInfo by simply
-         * calling onGetSuggestions.
+         * The default implementation splits the input text to words and returns
+         * {@link SentenceSuggestionsInfo} which contains suggestions for each word.
          * This function will run on the incoming IPC thread.
          * So, this is not called on the main thread,
          * but will be called in series on another thread.
@@ -156,14 +163,41 @@ public abstract class SpellCheckerService extends Service {
          */
         public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
                 int suggestionsLimit) {
-            final int length = textInfos.length;
-            final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[length];
-            for (int i = 0; i < length; ++i) {
-                final SuggestionsInfo si = onGetSuggestions(textInfos[i], suggestionsLimit);
-                si.setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence());
-                final int N = textInfos[i].getText().length();
-                retval[i] = new SentenceSuggestionsInfo(
-                        new SuggestionsInfo[] {si}, new int[]{0}, new int[]{N});
+            if (textInfos == null || textInfos.length == 0) {
+                return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+            }
+            if (DBG) {
+                Log.d(TAG, "onGetSentenceSuggestionsMultiple: + " + textInfos.length + ", "
+                        + suggestionsLimit);
+            }
+            if (mSentenceLevelAdapter == null) {
+                synchronized(this) {
+                    if (mSentenceLevelAdapter == null) {
+                        final String localeStr = getLocale();
+                        if (!TextUtils.isEmpty(localeStr)) {
+                            mSentenceLevelAdapter = new SentenceLevelAdapter(new Locale(localeStr));
+                        }
+                    }
+                }
+            }
+            if (mSentenceLevelAdapter == null) {
+                return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+            }
+            final int infosSize = textInfos.length;
+            final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize];
+            for (int i = 0; i < infosSize; ++i) {
+                final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams =
+                        mSentenceLevelAdapter.getSplitWords(textInfos[i]);
+                final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems =
+                        textInfoParams.mItems;
+                final int itemsSize = mItems.size();
+                final TextInfo[] splitTextInfos = new TextInfo[itemsSize];
+                for (int j = 0; j < itemsSize; ++j) {
+                    splitTextInfos[j] = mItems.get(j).mTextInfo;
+                }
+                retval[i] = SentenceLevelAdapter.reconstructSuggestions(
+                        textInfoParams, onGetSuggestionsMultiple(
+                                splitTextInfos, suggestionsLimit, true));
             }
             return retval;
         }
@@ -290,4 +324,135 @@ public abstract class SpellCheckerService extends Service {
             return internalSession;
         }
     }
+
+    /**
+     * Adapter class to accommodate word level spell checking APIs to sentence level spell checking
+     * APIs used in
+     * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)}
+     */
+    private static class SentenceLevelAdapter {
+        public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
+                new SentenceSuggestionsInfo[] {};
+        private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null);
+        /**
+         * Container for split TextInfo parameters
+         */
+        public static class SentenceWordItem {
+            public final TextInfo mTextInfo;
+            public final int mStart;
+            public final int mLength;
+            public SentenceWordItem(TextInfo ti, int start, int end) {
+                mTextInfo = ti;
+                mStart = start;
+                mLength = end - start;
+            }
+        }
+
+        /**
+         * Container for originally queried TextInfo and parameters
+         */
+        public static class SentenceTextInfoParams {
+            final TextInfo mOriginalTextInfo;
+            final ArrayList<SentenceWordItem> mItems;
+            final int mSize;
+            public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) {
+                mOriginalTextInfo = ti;
+                mItems = items;
+                mSize = items.size();
+            }
+        }
+
+        private final WordIterator mWordIterator;
+        public SentenceLevelAdapter(Locale locale) {
+            mWordIterator = new WordIterator(locale);
+        }
+
+        private SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
+            final WordIterator wordIterator = mWordIterator;
+            final CharSequence originalText = originalTextInfo.getText();
+            final int cookie = originalTextInfo.getCookie();
+            final int start = 0;
+            final int end = originalText.length();
+            final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
+            wordIterator.setCharSequence(originalText, 0, originalText.length());
+            int wordEnd = wordIterator.following(start);
+            int wordStart = wordIterator.getBeginning(wordEnd);
+            if (DBG) {
+                Log.d(TAG, "iterator: break: ---- 1st word start = " + wordStart + ", end = "
+                        + wordEnd + "\n" + originalText);
+            }
+            while (wordStart <= end && wordEnd != BreakIterator.DONE
+                    && wordStart != BreakIterator.DONE) {
+                if (wordEnd >= start && wordEnd > wordStart) {
+                    final String query = originalText.subSequence(wordStart, wordEnd).toString();
+                    final TextInfo ti = new TextInfo(query, cookie, query.hashCode());
+                    wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
+                    if (DBG) {
+                        Log.d(TAG, "Adapter: word (" + (wordItems.size() - 1) + ") " + query);
+                    }
+                }
+                wordEnd = wordIterator.following(wordEnd);
+                if (wordEnd == BreakIterator.DONE) {
+                    break;
+                }
+                wordStart = wordIterator.getBeginning(wordEnd);
+            }
+            if (originalText.length() >= SpellChecker.WORD_ITERATOR_INTERVAL
+                    && wordItems.size() >= 2) {
+                if (DBG) {
+                    Log.w(TAG, "Remove possibly divided word: "
+                            + wordItems.get(0).mTextInfo.getText());
+                }
+                wordItems.remove(0);
+            }
+            return new SentenceTextInfoParams(originalTextInfo, wordItems);
+        }
+
+        public static SentenceSuggestionsInfo reconstructSuggestions(
+                SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
+            if (results == null || results.length == 0) {
+                return null;
+            }
+            if (DBG) {
+                Log.w(TAG, "Adapter: onGetSuggestions: got " + results.length);
+            }
+            if (originalTextInfoParams == null) {
+                if (DBG) {
+                    Log.w(TAG, "Adapter: originalTextInfoParams is null.");
+                }
+                return null;
+            }
+            final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie();
+            final int originalSequence =
+                    originalTextInfoParams.mOriginalTextInfo.getSequence();
+
+            final int querySize = originalTextInfoParams.mSize;
+            final int[] offsets = new int[querySize];
+            final int[] lengths = new int[querySize];
+            final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize];
+            for (int i = 0; i < querySize; ++i) {
+                final SentenceWordItem item = originalTextInfoParams.mItems.get(i);
+                SuggestionsInfo result = null;
+                for (int j = 0; j < results.length; ++j) {
+                    final SuggestionsInfo cur = results[j];
+                    if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) {
+                        result = cur;
+                        result.setCookieAndSequence(originalCookie, originalSequence);
+                        break;
+                    }
+                }
+                offsets[i] = item.mStart;
+                lengths[i] = item.mLength;
+                reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO;
+                if (DBG) {
+                    final int size = reconstructedSuggestions[i].getSuggestionsCount();
+                    Log.w(TAG, "reconstructedSuggestions(" + i + ")" + size + ", first = "
+                            + (size > 0 ? reconstructedSuggestions[i].getSuggestionAt(0)
+                                    : "<none>") + ", offset = " + offsets[i] + ", length = "
+                            + lengths[i]);
+                }
+            }
+            return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths);
+        }
+    }
 }
index d05c1af..2167862 100644 (file)
@@ -45,7 +45,6 @@ public final class SpellCheckerInfo implements Parcelable {
     private final ResolveInfo mService;
     private final String mId;
     private final int mLabel;
-    private final boolean mSupportsSentenceSpellCheck;
 
     /**
      * The spell checker setting activity's name, used by the system settings to
@@ -98,9 +97,6 @@ public final class SpellCheckerInfo implements Parcelable {
             label = sa.getResourceId(com.android.internal.R.styleable.SpellChecker_label, 0);
             settingsActivityComponent = sa.getString(
                     com.android.internal.R.styleable.SpellChecker_settingsActivity);
-            mSupportsSentenceSpellCheck = sa.getBoolean(
-                    com.android.internal.R.styleable.SpellChecker_supportsSentenceSpellCheck,
-                    false);
             sa.recycle();
 
             final int depth = parser.getDepth();
@@ -142,7 +138,6 @@ public final class SpellCheckerInfo implements Parcelable {
      */
     public SpellCheckerInfo(Parcel source) {
         mLabel = source.readInt();
-        mSupportsSentenceSpellCheck = source.readInt() != 0;
         mId = source.readString();
         mSettingsActivityName = source.readString();
         mService = ResolveInfo.CREATOR.createFromParcel(source);
@@ -158,13 +153,6 @@ public final class SpellCheckerInfo implements Parcelable {
     }
 
     /**
-     * @hide
-     */
-    public boolean isSentenceSpellCheckSupported() {
-        return mSupportsSentenceSpellCheck;
-    }
-
-    /**
      * Return the component of the service that implements.
      */
     public ComponentName getComponent() {
@@ -188,7 +176,6 @@ public final class SpellCheckerInfo implements Parcelable {
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mLabel);
-        dest.writeInt(mSupportsSentenceSpellCheck ? 1 : 0);
         dest.writeString(mId);
         dest.writeString(mSettingsActivityName);
         mService.writeToParcel(dest, flags);
index 9dc05e4..628da3c 100644 (file)
@@ -91,8 +91,6 @@ public class SpellCheckerSession {
      * This meta-data must reference an XML resource.
      **/
     public static final String SERVICE_META_DATA = "android.view.textservice.scs";
-    private static final String SUPPORT_SENTENCE_SPELL_CHECK = "SupportSentenceSpellCheck";
-
 
     private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
     private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;
@@ -191,7 +189,9 @@ public class SpellCheckerSession {
      * Get candidate strings for a substring of the specified text.
      * @param textInfo text metadata for a spell checker
      * @param suggestionsLimit the maximum number of suggestions that will be returned
+     * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
      */
+    @Deprecated
     public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
         getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
     }
@@ -201,13 +201,14 @@ public class SpellCheckerSession {
      * @param textInfos an array of text metadata for a spell checker
      * @param suggestionsLimit the maximum number of suggestions that will be returned
      * @param sequentialWords true if textInfos can be treated as sequential words.
+     * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
      */
+    @Deprecated
     public void getSuggestions(
             TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
         if (DBG) {
             Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
         }
-        // TODO: Handle multiple words suggestions by using WordBreakIterator
         mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
                 textInfos, suggestionsLimit, sequentialWords);
     }
@@ -281,7 +282,7 @@ public class SpellCheckerSession {
                         break;
                     case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
                         if (DBG) {
-                            Log.w(TAG, "Get suggestions from the spell checker.");
+                            Log.w(TAG, "Get sentence suggestions from the spell checker.");
                         }
                         try {
                             session.onGetSentenceSuggestionsMultiple(
@@ -492,11 +493,4 @@ public class SpellCheckerSession {
     public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
         return mSpellCheckerSessionListenerImpl;
     }
-
-    /**
-     * @return true if the spell checker supports sentence level spell checking APIs
-     */
-    public boolean isSentenceSpellCheckSupported() {
-        return mSubtype.containsExtraValueKey(SUPPORT_SENTENCE_SPELL_CHECK);
-    }
 }
index c725b64..e188207 100644 (file)
@@ -116,7 +116,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
                     null /* Bundle not currently used by the textServicesManager */,
                     mCurrentLocale, this,
                     false /* means any available languages from current spell checker */);
-            mIsSentenceSpellCheckSupported = mSpellCheckerSession.isSentenceSpellCheckSupported();
+            mIsSentenceSpellCheckSupported = true;
         }
 
         // Restore SpellCheckSpans in pool
index de24d10..600a834 100755 (executable)
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. -->
         <attr name="settingsActivity"/>
-        <!-- Set true when the spell checker supports sentence level spell checking. -->
-        <attr name="supportsSentenceSpellCheck" format="boolean" />
     </declare-styleable>
 
     <!-- This is the subtype of the spell checker. Subtype can describe locales (e.g. en_US, fr_FR...) -->
index 4a5e442..8ac94fb 100644 (file)
 
   <public type="attr" name="parentActivityName" />
 
-  <public type="attr" name="supportsSentenceSpellCheck" />
-
   <public type="attr" name="importantForAccessibility"/>
 
 </resources>