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