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