/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package android.service.textservice; import com.android.internal.textservice.ISpellCheckerService; import com.android.internal.textservice.ISpellCheckerServiceCallback; import com.android.internal.textservice.ISpellCheckerSession; import com.android.internal.textservice.ISpellCheckerSessionListener; import android.app.Service; import android.content.Intent; 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 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. * This class combines a service to the system with the spell checker service interface that * spell checker must implement. * *
In addition to the normal Service lifecycle methods, this class * introduces a new specific callback that subclasses should override * {@link #createSession()} to provide a spell checker session that is corresponding * to requested language and so on. The spell checker session returned by this method * should extend {@link SpellCheckerService.Session}. *
* *{@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} * should return spell check results. * It receives {@link android.view.textservice.TextInfo} and returns * {@link android.view.textservice.SuggestionsInfo} for the input. * You may want to override * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} for * better performance and quality. *
* *Please note that {@link SpellCheckerService.Session#getLocale()} does not return a valid * locale before {@link SpellCheckerService.Session#onCreate()}
* */ public abstract class SpellCheckerService extends Service { private static final String TAG = SpellCheckerService.class.getSimpleName(); private static final boolean DBG = false; public static final String SERVICE_INTERFACE = "android.service.textservice.SpellCheckerService"; private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this); /** * Implement to return the implementation of the internal spell checker * service interface. Subclasses should not override. */ @Override public final IBinder onBind(final Intent intent) { if (DBG) { Log.w(TAG, "onBind"); } return mBinder; } /** * Factory method to create a spell checker session impl * @return SpellCheckerSessionImpl which should be overridden by a concrete implementation. */ public abstract Session createSession(); /** * This abstract class should be overridden by a concrete implementation of a spell checker. */ public static abstract class Session { private InternalISpellCheckerSession mInternalSession; private volatile SentenceLevelAdapter mSentenceLevelAdapter; /** * @hide */ public final void setInternalISpellCheckerSession(InternalISpellCheckerSession session) { mInternalSession = session; } /** * This is called after the class is initialized, at which point it knows it can call * getLocale() etc... */ public abstract void onCreate(); /** * Get suggestions for specified text in TextInfo. * 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. * @param textInfo the text metadata * @param suggestionsLimit the maximum number of suggestions to be returned * @return SuggestionsInfo which contains suggestions for textInfo */ public abstract SuggestionsInfo onGetSuggestions(TextInfo textInfo, int suggestionsLimit); /** * A batch process of onGetSuggestions. * 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. * @param textInfos an array of the text metadata * @param suggestionsLimit the maximum number of suggestions to be returned * @param sequentialWords true if textInfos can be treated as sequential words. * @return an array of {@link SentenceSuggestionsInfo} returned by * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} */ public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { final int length = textInfos.length; final SuggestionsInfo[] retval = new SuggestionsInfo[length]; for (int i = 0; i < length; ++i) { retval[i] = onGetSuggestions(textInfos[i], suggestionsLimit); retval[i].setCookieAndSequence( textInfos[i].getCookie(), textInfos[i].getSequence()); } return retval; } /** * Get sentence suggestions for specified texts in an array of TextInfo. * 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. * When you override this method, make sure that suggestionsLimit is applied to suggestions * that share the same start position and length. * @param textInfos an array of the text metadata * @param suggestionsLimit the maximum number of suggestions to be returned * @return an array of {@link SentenceSuggestionsInfo} returned by * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} */ public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) { 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 ArrayListNote: This is an internal protocol used by the system to establish spell checker * sessions, which is not guaranteed to be stable and is subject to change.
* * @param locale locale to be returned from {@link Session#getLocale()} * @param listener IPC channel object to be used to implement * {@link Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} and * {@link Session#onGetSuggestions(TextInfo, int)} * @param bundle bundle to be returned from {@link Session#getBundle()} * @param callback IPC channel to return the result to the caller in an asynchronous manner */ @Override public void getISpellCheckerSession( String locale, ISpellCheckerSessionListener listener, Bundle bundle, ISpellCheckerServiceCallback callback) { final SpellCheckerService service = mInternalServiceRef.get(); final InternalISpellCheckerSession internalSession; if (service == null) { // If the owner SpellCheckerService object was already destroyed and got GC-ed, // the weak-reference returns null and we should just ignore this request. internalSession = null; } else { final Session session = service.createSession(); internalSession = new InternalISpellCheckerSession(locale, listener, bundle, session); session.onCreate(); } try { callback.onSessionCreated(internalSession); } catch (RemoteException e) { } } } /** * 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