InputConnectionWrapper.java revision d8636ea7ca78df83d6b04088eab7853f15f3e999
1/* 2 * Copyright (C) 2008 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 com.android.internal.view; 18 19import android.os.Bundle; 20import android.os.RemoteException; 21import android.os.SystemClock; 22import android.util.Log; 23import android.view.KeyEvent; 24import android.view.inputmethod.CompletionInfo; 25import android.view.inputmethod.CorrectionInfo; 26import android.view.inputmethod.ExtractedText; 27import android.view.inputmethod.ExtractedTextRequest; 28import android.view.inputmethod.InputConnection; 29 30public class InputConnectionWrapper implements InputConnection { 31 private static final int MAX_WAIT_TIME_MILLIS = 2000; 32 private final IInputContext mIInputContext; 33 34 static class InputContextCallback extends IInputContextCallback.Stub { 35 private static final String TAG = "InputConnectionWrapper.ICC"; 36 public int mSeq; 37 public boolean mHaveValue; 38 public CharSequence mTextBeforeCursor; 39 public CharSequence mTextAfterCursor; 40 public CharSequence mSelectedText; 41 public ExtractedText mExtractedText; 42 public int mCursorCapsMode; 43 public boolean mRequestUpdateCursorAnchorInfoResult; 44 45 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain 46 // exclusive access to this object. 47 private static InputContextCallback sInstance = new InputContextCallback(); 48 private static int sSequenceNumber = 1; 49 50 /** 51 * Returns an InputContextCallback object that is guaranteed not to be in use by 52 * any other thread. The returned object's 'have value' flag is cleared and its expected 53 * sequence number is set to a new integer. We use a sequence number so that replies that 54 * occur after a timeout has expired are not interpreted as replies to a later request. 55 */ 56 private static InputContextCallback getInstance() { 57 synchronized (InputContextCallback.class) { 58 // Return sInstance if it's non-null, otherwise construct a new callback 59 InputContextCallback callback; 60 if (sInstance != null) { 61 callback = sInstance; 62 sInstance = null; 63 64 // Reset the callback 65 callback.mHaveValue = false; 66 } else { 67 callback = new InputContextCallback(); 68 } 69 70 // Set the sequence number 71 callback.mSeq = sSequenceNumber++; 72 return callback; 73 } 74 } 75 76 /** 77 * Makes the given InputContextCallback available for use in the future. 78 */ 79 private void dispose() { 80 synchronized (InputContextCallback.class) { 81 // If sInstance is non-null, just let this object be garbage-collected 82 if (sInstance == null) { 83 // Allow any objects being held to be gc'ed 84 mTextAfterCursor = null; 85 mTextBeforeCursor = null; 86 mExtractedText = null; 87 sInstance = this; 88 } 89 } 90 } 91 92 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) { 93 synchronized (this) { 94 if (seq == mSeq) { 95 mTextBeforeCursor = textBeforeCursor; 96 mHaveValue = true; 97 notifyAll(); 98 } else { 99 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 100 + ") in setTextBeforeCursor, ignoring."); 101 } 102 } 103 } 104 105 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) { 106 synchronized (this) { 107 if (seq == mSeq) { 108 mTextAfterCursor = textAfterCursor; 109 mHaveValue = true; 110 notifyAll(); 111 } else { 112 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 113 + ") in setTextAfterCursor, ignoring."); 114 } 115 } 116 } 117 118 public void setSelectedText(CharSequence selectedText, int seq) { 119 synchronized (this) { 120 if (seq == mSeq) { 121 mSelectedText = selectedText; 122 mHaveValue = true; 123 notifyAll(); 124 } else { 125 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 126 + ") in setSelectedText, ignoring."); 127 } 128 } 129 } 130 131 public void setCursorCapsMode(int capsMode, int seq) { 132 synchronized (this) { 133 if (seq == mSeq) { 134 mCursorCapsMode = capsMode; 135 mHaveValue = true; 136 notifyAll(); 137 } else { 138 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 139 + ") in setCursorCapsMode, ignoring."); 140 } 141 } 142 } 143 144 public void setExtractedText(ExtractedText extractedText, int seq) { 145 synchronized (this) { 146 if (seq == mSeq) { 147 mExtractedText = extractedText; 148 mHaveValue = true; 149 notifyAll(); 150 } else { 151 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 152 + ") in setExtractedText, ignoring."); 153 } 154 } 155 } 156 157 public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) { 158 synchronized (this) { 159 if (seq == mSeq) { 160 mRequestUpdateCursorAnchorInfoResult = result; 161 mHaveValue = true; 162 notifyAll(); 163 } else { 164 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq 165 + ") in setCursorAnchorInfoRequestResult, ignoring."); 166 } 167 } 168 } 169 170 /** 171 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds. 172 * 173 * <p>The caller must be synchronized on this callback object. 174 */ 175 void waitForResultLocked() { 176 long startTime = SystemClock.uptimeMillis(); 177 long endTime = startTime + MAX_WAIT_TIME_MILLIS; 178 179 while (!mHaveValue) { 180 long remainingTime = endTime - SystemClock.uptimeMillis(); 181 if (remainingTime <= 0) { 182 Log.w(TAG, "Timed out waiting on IInputContextCallback"); 183 return; 184 } 185 try { 186 wait(remainingTime); 187 } catch (InterruptedException e) { 188 } 189 } 190 } 191 } 192 193 public InputConnectionWrapper(IInputContext inputContext) { 194 mIInputContext = inputContext; 195 } 196 197 public CharSequence getTextAfterCursor(int length, int flags) { 198 CharSequence value = null; 199 try { 200 InputContextCallback callback = InputContextCallback.getInstance(); 201 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback); 202 synchronized (callback) { 203 callback.waitForResultLocked(); 204 if (callback.mHaveValue) { 205 value = callback.mTextAfterCursor; 206 } 207 } 208 callback.dispose(); 209 } catch (RemoteException e) { 210 return null; 211 } 212 return value; 213 } 214 215 public CharSequence getTextBeforeCursor(int length, int flags) { 216 CharSequence value = null; 217 try { 218 InputContextCallback callback = InputContextCallback.getInstance(); 219 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback); 220 synchronized (callback) { 221 callback.waitForResultLocked(); 222 if (callback.mHaveValue) { 223 value = callback.mTextBeforeCursor; 224 } 225 } 226 callback.dispose(); 227 } catch (RemoteException e) { 228 return null; 229 } 230 return value; 231 } 232 233 public CharSequence getSelectedText(int flags) { 234 CharSequence value = null; 235 try { 236 InputContextCallback callback = InputContextCallback.getInstance(); 237 mIInputContext.getSelectedText(flags, callback.mSeq, callback); 238 synchronized (callback) { 239 callback.waitForResultLocked(); 240 if (callback.mHaveValue) { 241 value = callback.mSelectedText; 242 } 243 } 244 callback.dispose(); 245 } catch (RemoteException e) { 246 return null; 247 } 248 return value; 249 } 250 251 public int getCursorCapsMode(int reqModes) { 252 int value = 0; 253 try { 254 InputContextCallback callback = InputContextCallback.getInstance(); 255 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback); 256 synchronized (callback) { 257 callback.waitForResultLocked(); 258 if (callback.mHaveValue) { 259 value = callback.mCursorCapsMode; 260 } 261 } 262 callback.dispose(); 263 } catch (RemoteException e) { 264 return 0; 265 } 266 return value; 267 } 268 269 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 270 ExtractedText value = null; 271 try { 272 InputContextCallback callback = InputContextCallback.getInstance(); 273 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback); 274 synchronized (callback) { 275 callback.waitForResultLocked(); 276 if (callback.mHaveValue) { 277 value = callback.mExtractedText; 278 } 279 } 280 callback.dispose(); 281 } catch (RemoteException e) { 282 return null; 283 } 284 return value; 285 } 286 287 public boolean commitText(CharSequence text, int newCursorPosition) { 288 try { 289 mIInputContext.commitText(text, newCursorPosition); 290 return true; 291 } catch (RemoteException e) { 292 return false; 293 } 294 } 295 296 public boolean commitCompletion(CompletionInfo text) { 297 try { 298 mIInputContext.commitCompletion(text); 299 return true; 300 } catch (RemoteException e) { 301 return false; 302 } 303 } 304 305 public boolean commitCorrection(CorrectionInfo correctionInfo) { 306 try { 307 mIInputContext.commitCorrection(correctionInfo); 308 return true; 309 } catch (RemoteException e) { 310 return false; 311 } 312 } 313 314 public boolean setSelection(int start, int end) { 315 try { 316 mIInputContext.setSelection(start, end); 317 return true; 318 } catch (RemoteException e) { 319 return false; 320 } 321 } 322 323 public boolean performEditorAction(int actionCode) { 324 try { 325 mIInputContext.performEditorAction(actionCode); 326 return true; 327 } catch (RemoteException e) { 328 return false; 329 } 330 } 331 332 public boolean performContextMenuAction(int id) { 333 try { 334 mIInputContext.performContextMenuAction(id); 335 return true; 336 } catch (RemoteException e) { 337 return false; 338 } 339 } 340 341 public boolean setComposingRegion(int start, int end) { 342 try { 343 mIInputContext.setComposingRegion(start, end); 344 return true; 345 } catch (RemoteException e) { 346 return false; 347 } 348 } 349 350 public boolean setComposingText(CharSequence text, int newCursorPosition) { 351 try { 352 mIInputContext.setComposingText(text, newCursorPosition); 353 return true; 354 } catch (RemoteException e) { 355 return false; 356 } 357 } 358 359 public boolean finishComposingText() { 360 try { 361 mIInputContext.finishComposingText(); 362 return true; 363 } catch (RemoteException e) { 364 return false; 365 } 366 } 367 368 public boolean beginBatchEdit() { 369 try { 370 mIInputContext.beginBatchEdit(); 371 return true; 372 } catch (RemoteException e) { 373 return false; 374 } 375 } 376 377 public boolean endBatchEdit() { 378 try { 379 mIInputContext.endBatchEdit(); 380 return true; 381 } catch (RemoteException e) { 382 return false; 383 } 384 } 385 386 public boolean sendKeyEvent(KeyEvent event) { 387 try { 388 mIInputContext.sendKeyEvent(event); 389 return true; 390 } catch (RemoteException e) { 391 return false; 392 } 393 } 394 395 public boolean clearMetaKeyStates(int states) { 396 try { 397 mIInputContext.clearMetaKeyStates(states); 398 return true; 399 } catch (RemoteException e) { 400 return false; 401 } 402 } 403 404 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 405 try { 406 mIInputContext.deleteSurroundingText(beforeLength, afterLength); 407 return true; 408 } catch (RemoteException e) { 409 return false; 410 } 411 } 412 413 public boolean reportFullscreenMode(boolean enabled) { 414 try { 415 mIInputContext.reportFullscreenMode(enabled); 416 return true; 417 } catch (RemoteException e) { 418 return false; 419 } 420 } 421 422 public boolean performPrivateCommand(String action, Bundle data) { 423 try { 424 mIInputContext.performPrivateCommand(action, data); 425 return true; 426 } catch (RemoteException e) { 427 return false; 428 } 429 } 430 431 public boolean requestCursorUpdates(int cursorUpdateMode) { 432 boolean result = false; 433 try { 434 InputContextCallback callback = InputContextCallback.getInstance(); 435 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback); 436 synchronized (callback) { 437 callback.waitForResultLocked(); 438 if (callback.mHaveValue) { 439 result = callback.mRequestUpdateCursorAnchorInfoResult; 440 } 441 } 442 callback.dispose(); 443 } catch (RemoteException e) { 444 return false; 445 } 446 return result; 447 } 448 449 /** 450 * @removed 451 */ 452 public boolean requestUpdateCursorAnchorInfo(int cursorUpdateMode) { 453 return requestCursorUpdates(cursorUpdateMode); 454 } 455} 456