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