SpellCheckerService.java revision d404fe110558bd2e1960b428db6a2ee8bfd040cd
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package android.service.textservice; 18 19import com.android.internal.textservice.ISpellCheckerService; 20import com.android.internal.textservice.ISpellCheckerSession; 21import com.android.internal.textservice.ISpellCheckerSessionListener; 22 23import android.app.Service; 24import android.content.Intent; 25import android.os.Bundle; 26import android.os.IBinder; 27import android.os.Process; 28import android.os.RemoteException; 29import android.util.Log; 30import android.view.textservice.SentenceSuggestionsInfo; 31import android.view.textservice.SuggestionsInfo; 32import android.view.textservice.TextInfo; 33 34import java.lang.ref.WeakReference; 35 36/** 37 * SpellCheckerService provides an abstract base class for a spell checker. 38 * This class combines a service to the system with the spell checker service interface that 39 * spell checker must implement. 40 * 41 * <p>In addition to the normal Service lifecycle methods, this class 42 * introduces a new specific callback that subclasses should override 43 * {@link #createSession()} to provide a spell checker session that is corresponding 44 * to requested language and so on. The spell checker session returned by this method 45 * should extend {@link SpellCheckerService.Session}. 46 * </p> 47 * 48 * <h3>Returning spell check results</h3> 49 * 50 * <p>{@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} 51 * should return spell check results. 52 * It receives {@link android.view.textservice.TextInfo} and returns 53 * {@link android.view.textservice.SuggestionsInfo} for the input. 54 * You may want to override 55 * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} for 56 * better performance and quality. 57 * </p> 58 * 59 * <p>Please note that {@link SpellCheckerService.Session#getLocale()} does not return a valid 60 * locale before {@link SpellCheckerService.Session#onCreate()} </p> 61 * 62 */ 63public abstract class SpellCheckerService extends Service { 64 private static final String TAG = SpellCheckerService.class.getSimpleName(); 65 private static final boolean DBG = false; 66 public static final String SERVICE_INTERFACE = 67 "android.service.textservice.SpellCheckerService"; 68 69 private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this); 70 71 72 /** 73 * Implement to return the implementation of the internal spell checker 74 * service interface. Subclasses should not override. 75 */ 76 @Override 77 public final IBinder onBind(final Intent intent) { 78 if (DBG) { 79 Log.w(TAG, "onBind"); 80 } 81 return mBinder; 82 } 83 84 /** 85 * Factory method to create a spell checker session impl 86 * @return SpellCheckerSessionImpl which should be overridden by a concrete implementation. 87 */ 88 public abstract Session createSession(); 89 90 /** 91 * This abstract class should be overridden by a concrete implementation of a spell checker. 92 */ 93 public static abstract class Session { 94 private InternalISpellCheckerSession mInternalSession; 95 96 /** 97 * @hide 98 */ 99 public final void setInternalISpellCheckerSession(InternalISpellCheckerSession session) { 100 mInternalSession = session; 101 } 102 103 /** 104 * This is called after the class is initialized, at which point it knows it can call 105 * getLocale() etc... 106 */ 107 public abstract void onCreate(); 108 109 /** 110 * Get suggestions for specified text in TextInfo. 111 * This function will run on the incoming IPC thread. 112 * So, this is not called on the main thread, 113 * but will be called in series on another thread. 114 * @param textInfo the text metadata 115 * @param suggestionsLimit the number of limit of suggestions returned 116 * @return SuggestionsInfo which contains suggestions for textInfo 117 */ 118 public abstract SuggestionsInfo onGetSuggestions(TextInfo textInfo, int suggestionsLimit); 119 120 /** 121 * A batch process of onGetSuggestions. 122 * This function will run on the incoming IPC thread. 123 * So, this is not called on the main thread, 124 * but will be called in series on another thread. 125 * @param textInfos an array of the text metadata 126 * @param suggestionsLimit the number of limit of suggestions returned 127 * @param sequentialWords true if textInfos can be treated as sequential words. 128 * @return an array of SuggestionsInfo of onGetSuggestions 129 */ 130 public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, 131 int suggestionsLimit, boolean sequentialWords) { 132 final int length = textInfos.length; 133 final SuggestionsInfo[] retval = new SuggestionsInfo[length]; 134 for (int i = 0; i < length; ++i) { 135 retval[i] = onGetSuggestions(textInfos[i], suggestionsLimit); 136 retval[i].setCookieAndSequence( 137 textInfos[i].getCookie(), textInfos[i].getSequence()); 138 } 139 return retval; 140 } 141 142 /** 143 * @hide 144 * The default implementation returns an array of SentenceSuggestionsInfo by simply calling 145 * onGetSuggestions(). 146 * When you override this method, make sure that suggestionsLimit is applied to suggestions 147 * that share the same start position and length. 148 */ 149 public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, 150 int suggestionsLimit) { 151 final int length = textInfos.length; 152 final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[length]; 153 for (int i = 0; i < length; ++i) { 154 final SuggestionsInfo si = onGetSuggestions(textInfos[i], suggestionsLimit); 155 si.setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence()); 156 final int N = textInfos[i].getText().length(); 157 retval[i] = new SentenceSuggestionsInfo( 158 new SuggestionsInfo[] {si}, new int[]{0}, new int[]{N}); 159 } 160 return retval; 161 } 162 163 /** 164 * Request to abort all tasks executed in SpellChecker. 165 * This function will run on the incoming IPC thread. 166 * So, this is not called on the main thread, 167 * but will be called in series on another thread. 168 */ 169 public void onCancel() {} 170 171 /** 172 * Request to close this session. 173 * This function will run on the incoming IPC thread. 174 * So, this is not called on the main thread, 175 * but will be called in series on another thread. 176 */ 177 public void onClose() {} 178 179 /** 180 * @return Locale for this session 181 */ 182 public String getLocale() { 183 return mInternalSession.getLocale(); 184 } 185 186 /** 187 * @return Bundle for this session 188 */ 189 public Bundle getBundle() { 190 return mInternalSession.getBundle(); 191 } 192 } 193 194 // Preventing from exposing ISpellCheckerSession.aidl, create an internal class. 195 private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub { 196 private ISpellCheckerSessionListener mListener; 197 private final Session mSession; 198 private final String mLocale; 199 private final Bundle mBundle; 200 201 public InternalISpellCheckerSession(String locale, ISpellCheckerSessionListener listener, 202 Bundle bundle, Session session) { 203 mListener = listener; 204 mSession = session; 205 mLocale = locale; 206 mBundle = bundle; 207 session.setInternalISpellCheckerSession(this); 208 } 209 210 @Override 211 public void onGetSuggestionsMultiple( 212 TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { 213 int pri = Process.getThreadPriority(Process.myTid()); 214 try { 215 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 216 mListener.onGetSuggestions( 217 mSession.onGetSuggestionsMultiple( 218 textInfos, suggestionsLimit, sequentialWords)); 219 } catch (RemoteException e) { 220 } finally { 221 Process.setThreadPriority(pri); 222 } 223 } 224 225 @Override 226 public void onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) { 227 try { 228 mListener.onGetSentenceSuggestions( 229 mSession.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit)); 230 } catch (RemoteException e) { 231 } 232 } 233 234 @Override 235 public void onCancel() { 236 int pri = Process.getThreadPriority(Process.myTid()); 237 try { 238 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 239 mSession.onCancel(); 240 } finally { 241 Process.setThreadPriority(pri); 242 } 243 } 244 245 @Override 246 public void onClose() { 247 int pri = Process.getThreadPriority(Process.myTid()); 248 try { 249 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 250 mSession.onClose(); 251 } finally { 252 Process.setThreadPriority(pri); 253 mListener = null; 254 } 255 } 256 257 public String getLocale() { 258 return mLocale; 259 } 260 261 public Bundle getBundle() { 262 return mBundle; 263 } 264 } 265 266 private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub { 267 private final WeakReference<SpellCheckerService> mInternalServiceRef; 268 269 public SpellCheckerServiceBinder(SpellCheckerService service) { 270 mInternalServiceRef = new WeakReference<SpellCheckerService>(service); 271 } 272 273 @Override 274 public ISpellCheckerSession getISpellCheckerSession( 275 String locale, ISpellCheckerSessionListener listener, Bundle bundle) { 276 final SpellCheckerService service = mInternalServiceRef.get(); 277 if (service == null) return null; 278 final Session session = service.createSession(); 279 final InternalISpellCheckerSession internalSession = 280 new InternalISpellCheckerSession(locale, listener, bundle, session); 281 session.onCreate(); 282 return internalSession; 283 } 284 } 285} 286