/* * 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 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 com.android.internal.textservice.ISpellCheckerService; import com.android.internal.textservice.ISpellCheckerServiceCallback; import com.android.internal.textservice.ISpellCheckerSession; import com.android.internal.textservice.ISpellCheckerSessionListener; 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 ArrayListThe session implementation should not set attributes that are not included in the
* return value of {@code getSupportedAttributes()} when creating {@link SuggestionsInfo}.
*
* @return The supported result attributes for this session
*/
public @SuggestionsInfo.ResultAttrs int getSupportedAttributes() {
return mInternalSession.getSupportedAttributes();
}
}
// Preventing from exposing ISpellCheckerSession.aidl, create an internal class.
private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub {
private ISpellCheckerSessionListener mListener;
private final Session mSession;
private final String mLocale;
private final Bundle mBundle;
private final @SuggestionsInfo.ResultAttrs int mSupportedAttributes;
public InternalISpellCheckerSession(String locale, ISpellCheckerSessionListener listener,
Bundle bundle, Session session,
@SuggestionsInfo.ResultAttrs int supportedAttributes) {
mListener = listener;
mSession = session;
mLocale = locale;
mBundle = bundle;
mSupportedAttributes = supportedAttributes;
session.setInternalISpellCheckerSession(this);
}
@Override
public void onGetSuggestionsMultiple(
TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
int pri = Process.getThreadPriority(Process.myTid());
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mListener.onGetSuggestions(
mSession.onGetSuggestionsMultiple(
textInfos, suggestionsLimit, sequentialWords));
} catch (RemoteException e) {
} finally {
Process.setThreadPriority(pri);
}
}
@Override
public void onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
try {
mListener.onGetSentenceSuggestions(
mSession.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit));
} catch (RemoteException e) {
}
}
@Override
public void onCancel() {
int pri = Process.getThreadPriority(Process.myTid());
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mSession.onCancel();
} finally {
Process.setThreadPriority(pri);
}
}
@Override
public void onClose() {
int pri = Process.getThreadPriority(Process.myTid());
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mSession.onClose();
} finally {
Process.setThreadPriority(pri);
mListener = null;
}
}
public String getLocale() {
return mLocale;
}
public Bundle getBundle() {
return mBundle;
}
public @SuggestionsInfo.ResultAttrs int getSupportedAttributes() {
return mSupportedAttributes;
}
}
private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub {
private final WeakReference Note: 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.