SpellCheckerSession.java revision 5357806980269d846a15c845a6fcc0384fb18860
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view.textservice; 18 19import com.android.internal.textservice.ISpellCheckerSession; 20import com.android.internal.textservice.ISpellCheckerSessionListener; 21import com.android.internal.textservice.ITextServicesManager; 22import com.android.internal.textservice.ITextServicesSessionListener; 23 24import android.os.Handler; 25import android.os.Message; 26import android.os.RemoteException; 27import android.util.Log; 28import android.view.textservice.SpellCheckerInfo; 29import android.view.textservice.SuggestionsInfo; 30import android.view.textservice.TextInfo; 31 32import java.util.LinkedList; 33import java.util.Queue; 34 35/** 36 * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService. 37 */ 38public class SpellCheckerSession { 39 private static final String TAG = SpellCheckerSession.class.getSimpleName(); 40 private static final boolean DBG = false; 41 /** 42 * Name under which a SpellChecker service component publishes information about itself. 43 * This meta-data must reference an XML resource. 44 **/ 45 public static final String SERVICE_META_DATA = "android.view.textservice.scs"; 46 47 48 private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1; 49 50 private final InternalListener mInternalListener; 51 private final ITextServicesManager mTextServicesManager; 52 private final SpellCheckerInfo mSpellCheckerInfo; 53 private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl; 54 55 private boolean mIsUsed; 56 private SpellCheckerSessionListener mSpellCheckerSessionListener; 57 58 /** Handler that will execute the main tasks */ 59 private final Handler mHandler = new Handler() { 60 @Override 61 public void handleMessage(Message msg) { 62 switch (msg.what) { 63 case MSG_ON_GET_SUGGESTION_MULTIPLE: 64 handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj); 65 break; 66 } 67 } 68 }; 69 70 /** 71 * Constructor 72 * @hide 73 */ 74 public SpellCheckerSession( 75 SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) { 76 if (info == null || listener == null || tsm == null) { 77 throw new NullPointerException(); 78 } 79 mSpellCheckerInfo = info; 80 mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler); 81 mInternalListener = new InternalListener(); 82 mTextServicesManager = tsm; 83 mIsUsed = true; 84 mSpellCheckerSessionListener = listener; 85 } 86 87 /** 88 * @return true if the connection to a text service of this session is disconnected and not 89 * alive. 90 */ 91 public boolean isSessionDisconnected() { 92 return mSpellCheckerSessionListenerImpl.isDisconnected(); 93 } 94 95 /** 96 * Get the spell checker service info this spell checker session has. 97 * @return SpellCheckerInfo for the specified locale. 98 */ 99 public SpellCheckerInfo getSpellChecker() { 100 return mSpellCheckerInfo; 101 } 102 103 /** 104 * Finish this session and allow TextServicesManagerService to disconnect the bound spell 105 * checker. 106 */ 107 public void close() { 108 mIsUsed = false; 109 try { 110 mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl); 111 } catch (RemoteException e) { 112 // do nothing 113 } 114 } 115 116 /** 117 * Get candidate strings for a substring of the specified text. 118 * @param textInfo text metadata for a spell checker 119 * @param suggestionsLimit the number of limit of suggestions returned 120 */ 121 public void getSuggestions(TextInfo textInfo, int suggestionsLimit) { 122 getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false); 123 } 124 125 /** 126 * A batch process of getSuggestions 127 * @param textInfos an array of text metadata for a spell checker 128 * @param suggestionsLimit the number of limit of suggestions returned 129 * @param sequentialWords true if textInfos can be treated as sequential words. 130 */ 131 public void getSuggestions( 132 TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { 133 if (DBG) { 134 Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId()); 135 } 136 // TODO: Handle multiple words suggestions by using WordBreakIterator 137 mSpellCheckerSessionListenerImpl.getSuggestionsMultiple( 138 textInfos, suggestionsLimit, sequentialWords); 139 } 140 141 private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) { 142 mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos); 143 } 144 145 private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub { 146 private static final int TASK_CANCEL = 1; 147 private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2; 148 private final Queue<SpellCheckerParams> mPendingTasks = 149 new LinkedList<SpellCheckerParams>(); 150 private final Handler mHandler; 151 152 private boolean mOpened; 153 private ISpellCheckerSession mISpellCheckerSession; 154 155 public SpellCheckerSessionListenerImpl(Handler handler) { 156 mOpened = false; 157 mHandler = handler; 158 } 159 160 private static class SpellCheckerParams { 161 public final int mWhat; 162 public final TextInfo[] mTextInfos; 163 public final int mSuggestionsLimit; 164 public final boolean mSequentialWords; 165 public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit, 166 boolean sequentialWords) { 167 mWhat = what; 168 mTextInfos = textInfos; 169 mSuggestionsLimit = suggestionsLimit; 170 mSequentialWords = sequentialWords; 171 } 172 } 173 174 private void processTask(SpellCheckerParams scp) { 175 switch (scp.mWhat) { 176 case TASK_CANCEL: 177 processCancel(); 178 break; 179 case TASK_GET_SUGGESTIONS_MULTIPLE: 180 processGetSuggestionsMultiple(scp); 181 break; 182 } 183 } 184 185 public synchronized void onServiceConnected(ISpellCheckerSession session) { 186 mISpellCheckerSession = session; 187 mOpened = true; 188 if (DBG) 189 Log.d(TAG, "onServiceConnected - Success"); 190 while (!mPendingTasks.isEmpty()) { 191 processTask(mPendingTasks.poll()); 192 } 193 } 194 195 public void getSuggestionsMultiple( 196 TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { 197 if (DBG) { 198 Log.w(TAG, "getSuggestionsMultiple"); 199 } 200 processOrEnqueueTask( 201 new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos, 202 suggestionsLimit, sequentialWords)); 203 } 204 205 public boolean isDisconnected() { 206 return mOpened && mISpellCheckerSession == null; 207 } 208 209 public boolean checkOpenConnection() { 210 if (mISpellCheckerSession != null) { 211 return true; 212 } 213 Log.e(TAG, "not connected to the spellchecker service."); 214 return false; 215 } 216 217 private void processOrEnqueueTask(SpellCheckerParams scp) { 218 if (DBG) { 219 Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession); 220 } 221 if (mISpellCheckerSession == null) { 222 mPendingTasks.offer(scp); 223 } else { 224 processTask(scp); 225 } 226 } 227 228 private void processCancel() { 229 if (!checkOpenConnection()) { 230 return; 231 } 232 if (DBG) { 233 Log.w(TAG, "Cancel spell checker tasks."); 234 } 235 try { 236 mISpellCheckerSession.onCancel(); 237 } catch (RemoteException e) { 238 Log.e(TAG, "Failed to cancel " + e); 239 } 240 } 241 242 private void processGetSuggestionsMultiple(SpellCheckerParams scp) { 243 if (!checkOpenConnection()) { 244 return; 245 } 246 if (DBG) { 247 Log.w(TAG, "Get suggestions from the spell checker."); 248 } 249 try { 250 mISpellCheckerSession.onGetSuggestionsMultiple( 251 scp.mTextInfos, scp.mSuggestionsLimit, scp.mSequentialWords); 252 } catch (RemoteException e) { 253 Log.e(TAG, "Failed to get suggestions " + e); 254 } 255 } 256 257 @Override 258 public void onGetSuggestions(SuggestionsInfo[] results) { 259 mHandler.sendMessage(Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE, results)); 260 } 261 } 262 263 /** 264 * Callback for getting results from text services 265 */ 266 public interface SpellCheckerSessionListener { 267 /** 268 * Callback for "getSuggestions" 269 * @param results an array of results of getSuggestions 270 */ 271 public void onGetSuggestions(SuggestionsInfo[] results); 272 } 273 274 private class InternalListener extends ITextServicesSessionListener.Stub { 275 @Override 276 public void onServiceConnected(ISpellCheckerSession session) { 277 if (DBG) { 278 Log.w(TAG, "SpellCheckerSession connected."); 279 } 280 mSpellCheckerSessionListenerImpl.onServiceConnected(session); 281 } 282 } 283 284 @Override 285 protected void finalize() throws Throwable { 286 super.finalize(); 287 if (mIsUsed) { 288 Log.e(TAG, "SpellCheckerSession was not finished properly." + 289 "You should call finishShession() when you finished to use a spell checker."); 290 close(); 291 } 292 } 293 294 /** 295 * @hide 296 */ 297 public ITextServicesSessionListener getTextServicesSessionListener() { 298 return mInternalListener; 299 } 300 301 /** 302 * @hide 303 */ 304 public ISpellCheckerSessionListener getSpellCheckerSessionListener() { 305 return mSpellCheckerSessionListenerImpl; 306 } 307} 308