InputConnectionWrapper.java revision d24b8183b93e781080b2c16c487e60d51c12da31
1package com.android.internal.view; 2 3import com.android.internal.view.IInputContext; 4 5import android.os.Bundle; 6import android.os.RemoteException; 7import android.os.SystemClock; 8import android.util.Log; 9import android.view.KeyEvent; 10import android.view.inputmethod.CompletionInfo; 11import android.view.inputmethod.ExtractedText; 12import android.view.inputmethod.ExtractedTextRequest; 13import android.view.inputmethod.InputConnection; 14 15public class InputConnectionWrapper implements InputConnection { 16 private static final int MAX_WAIT_TIME_MILLIS = 2000; 17 private final IInputContext mIInputContext; 18 19 static class InputContextCallback extends IInputContextCallback.Stub { 20 private static final String TAG = "InputConnectionWrapper.ICC"; 21 public int mSeq; 22 public boolean mHaveValue; 23 public CharSequence mTextBeforeCursor; 24 public CharSequence mTextAfterCursor; 25 public ExtractedText mExtractedText; 26 public int mCursorCapsMode; 27 28 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain 29 // exclusive access to this object. 30 private static InputContextCallback sInstance = new InputContextCallback(); 31 private static int sSequenceNumber = 1; 32 33 /** 34 * Returns an InputContextCallback object that is guaranteed not to be in use by 35 * any other thread. The returned object's 'have value' flag is cleared and its expected 36 * sequence number is set to a new integer. We use a sequence number so that replies that 37 * occur after a timeout has expired are not interpreted as replies to a later request. 38 */ 39 private static InputContextCallback getInstance() { 40 synchronized (InputContextCallback.class) { 41 // Return sInstance if it's non-null, otherwise construct a new callback 42 InputContextCallback callback; 43 if (sInstance != null) { 44 callback = sInstance; 45 sInstance = null; 46 47 // Reset the callback 48 callback.mHaveValue = false; 49 } else { 50 callback = new InputContextCallback(); 51 } 52 53 // Set the sequence number 54 callback.mSeq = sSequenceNumber++; 55 return callback; 56 } 57 } 58 59 /** 60 * Makes the given InputContextCallback available for use in the future. 61 */ 62 private void dispose() { 63 synchronized (InputContextCallback.class) { 64 // If sInstance is non-null, just let this object be garbage-collected 65 if (sInstance == null) { 66 // Allow any objects being held to be gc'ed 67 mTextAfterCursor = null; 68 mTextBeforeCursor = null; 69 mExtractedText = null; 70 sInstance = this; 71 } 72 } 73 } 74 75 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) { 76 synchronized (this) { 77 if (seq == mSeq) { 78 mTextBeforeCursor = textBeforeCursor; 79 mHaveValue = true; 80 notifyAll(); 81 } else { 82 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 83 + ") in setTextBeforeCursor, ignoring."); 84 } 85 } 86 } 87 88 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) { 89 synchronized (this) { 90 if (seq == mSeq) { 91 mTextAfterCursor = textAfterCursor; 92 mHaveValue = true; 93 notifyAll(); 94 } else { 95 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 96 + ") in setTextAfterCursor, ignoring."); 97 } 98 } 99 } 100 101 public void setCursorCapsMode(int capsMode, int seq) { 102 synchronized (this) { 103 if (seq == mSeq) { 104 mCursorCapsMode = capsMode; 105 mHaveValue = true; 106 notifyAll(); 107 } else { 108 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 109 + ") in setCursorCapsMode, ignoring."); 110 } 111 } 112 } 113 114 public void setExtractedText(ExtractedText extractedText, int seq) { 115 synchronized (this) { 116 if (seq == mSeq) { 117 mExtractedText = extractedText; 118 mHaveValue = true; 119 notifyAll(); 120 } else { 121 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 122 + ") in setExtractedText, ignoring."); 123 } 124 } 125 } 126 127 /** 128 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds. 129 * 130 * <p>The caller must be synchronized on this callback object. 131 */ 132 void waitForResultLocked() { 133 long startTime = SystemClock.uptimeMillis(); 134 long endTime = startTime + MAX_WAIT_TIME_MILLIS; 135 136 while (!mHaveValue) { 137 long remainingTime = endTime - SystemClock.uptimeMillis(); 138 if (remainingTime <= 0) { 139 Log.w(TAG, "Timed out waiting on IInputContextCallback"); 140 return; 141 } 142 try { 143 wait(remainingTime); 144 } catch (InterruptedException e) { 145 } 146 } 147 } 148 } 149 150 public InputConnectionWrapper(IInputContext inputContext) { 151 mIInputContext = inputContext; 152 } 153 154 public CharSequence getTextAfterCursor(int length, int flags) { 155 CharSequence value = null; 156 try { 157 InputContextCallback callback = InputContextCallback.getInstance(); 158 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback); 159 synchronized (callback) { 160 callback.waitForResultLocked(); 161 if (callback.mHaveValue) { 162 value = callback.mTextAfterCursor; 163 } 164 } 165 callback.dispose(); 166 } catch (RemoteException e) { 167 return null; 168 } 169 return value; 170 } 171 172 public CharSequence getTextBeforeCursor(int length, int flags) { 173 CharSequence value = null; 174 try { 175 InputContextCallback callback = InputContextCallback.getInstance(); 176 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback); 177 synchronized (callback) { 178 callback.waitForResultLocked(); 179 if (callback.mHaveValue) { 180 value = callback.mTextBeforeCursor; 181 } 182 } 183 callback.dispose(); 184 } catch (RemoteException e) { 185 return null; 186 } 187 return value; 188 } 189 190 public int getCursorCapsMode(int reqModes) { 191 int value = 0; 192 try { 193 InputContextCallback callback = InputContextCallback.getInstance(); 194 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback); 195 synchronized (callback) { 196 callback.waitForResultLocked(); 197 if (callback.mHaveValue) { 198 value = callback.mCursorCapsMode; 199 } 200 } 201 callback.dispose(); 202 } catch (RemoteException e) { 203 return 0; 204 } 205 return value; 206 } 207 208 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 209 ExtractedText value = null; 210 try { 211 InputContextCallback callback = InputContextCallback.getInstance(); 212 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback); 213 synchronized (callback) { 214 callback.waitForResultLocked(); 215 if (callback.mHaveValue) { 216 value = callback.mExtractedText; 217 } 218 } 219 callback.dispose(); 220 } catch (RemoteException e) { 221 return null; 222 } 223 return value; 224 } 225 226 public boolean commitText(CharSequence text, int newCursorPosition) { 227 try { 228 mIInputContext.commitText(text, newCursorPosition); 229 return true; 230 } catch (RemoteException e) { 231 return false; 232 } 233 } 234 235 public boolean commitCompletion(CompletionInfo text) { 236 try { 237 mIInputContext.commitCompletion(text); 238 return true; 239 } catch (RemoteException e) { 240 return false; 241 } 242 } 243 244 public boolean setSelection(int start, int end) { 245 try { 246 mIInputContext.setSelection(start, end); 247 return true; 248 } catch (RemoteException e) { 249 return false; 250 } 251 } 252 253 public boolean performContextMenuAction(int id) { 254 try { 255 mIInputContext.performContextMenuAction(id); 256 return true; 257 } catch (RemoteException e) { 258 return false; 259 } 260 } 261 262 public boolean setComposingText(CharSequence text, int newCursorPosition) { 263 try { 264 mIInputContext.setComposingText(text, newCursorPosition); 265 return true; 266 } catch (RemoteException e) { 267 return false; 268 } 269 } 270 271 public boolean finishComposingText() { 272 try { 273 mIInputContext.finishComposingText(); 274 return true; 275 } catch (RemoteException e) { 276 return false; 277 } 278 } 279 280 public boolean beginBatchEdit() { 281 try { 282 mIInputContext.beginBatchEdit(); 283 return true; 284 } catch (RemoteException e) { 285 return false; 286 } 287 } 288 289 public boolean endBatchEdit() { 290 try { 291 mIInputContext.endBatchEdit(); 292 return true; 293 } catch (RemoteException e) { 294 return false; 295 } 296 } 297 298 public boolean sendKeyEvent(KeyEvent event) { 299 try { 300 mIInputContext.sendKeyEvent(event); 301 return true; 302 } catch (RemoteException e) { 303 return false; 304 } 305 } 306 307 public boolean clearMetaKeyStates(int states) { 308 try { 309 mIInputContext.clearMetaKeyStates(states); 310 return true; 311 } catch (RemoteException e) { 312 return false; 313 } 314 } 315 316 public boolean deleteSurroundingText(int leftLength, int rightLength) { 317 try { 318 mIInputContext.deleteSurroundingText(leftLength, rightLength); 319 return true; 320 } catch (RemoteException e) { 321 return false; 322 } 323 } 324 325 public boolean hideStatusIcon() { 326 try { 327 mIInputContext.showStatusIcon(null, 0); 328 return true; 329 } catch (RemoteException e) { 330 return false; 331 } 332 } 333 334 public boolean showStatusIcon(String packageName, int resId) { 335 try { 336 mIInputContext.showStatusIcon(packageName, resId); 337 return true; 338 } catch (RemoteException e) { 339 return false; 340 } 341 } 342 343 public boolean performPrivateCommand(String action, Bundle data) { 344 try { 345 mIInputContext.performPrivateCommand(action, data); 346 return true; 347 } catch (RemoteException e) { 348 return false; 349 } 350 } 351} 352