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