2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.view;
19 import android.os.Bundle;
20 import android.os.Handler;
21 import android.os.RemoteException;
22 import android.os.SystemClock;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.inputmethod.CompletionInfo;
26 import android.view.inputmethod.CorrectionInfo;
27 import android.view.inputmethod.ExtractedText;
28 import android.view.inputmethod.ExtractedTextRequest;
29 import android.view.inputmethod.InputConnection;
30 import android.view.inputmethod.InputConnectionInspector;
31 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
33 public class InputConnectionWrapper implements InputConnection {
34 private static final int MAX_WAIT_TIME_MILLIS = 2000;
35 private final IInputContext mIInputContext;
37 private final int mMissingMethods;
39 static class InputContextCallback extends IInputContextCallback.Stub {
40 private static final String TAG = "InputConnectionWrapper.ICC";
42 public boolean mHaveValue;
43 public CharSequence mTextBeforeCursor;
44 public CharSequence mTextAfterCursor;
45 public CharSequence mSelectedText;
46 public ExtractedText mExtractedText;
47 public int mCursorCapsMode;
48 public boolean mRequestUpdateCursorAnchorInfoResult;
50 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
51 // exclusive access to this object.
52 private static InputContextCallback sInstance = new InputContextCallback();
53 private static int sSequenceNumber = 1;
56 * Returns an InputContextCallback object that is guaranteed not to be in use by
57 * any other thread. The returned object's 'have value' flag is cleared and its expected
58 * sequence number is set to a new integer. We use a sequence number so that replies that
59 * occur after a timeout has expired are not interpreted as replies to a later request.
61 private static InputContextCallback getInstance() {
62 synchronized (InputContextCallback.class) {
63 // Return sInstance if it's non-null, otherwise construct a new callback
64 InputContextCallback callback;
65 if (sInstance != null) {
70 callback.mHaveValue = false;
72 callback = new InputContextCallback();
75 // Set the sequence number
76 callback.mSeq = sSequenceNumber++;
82 * Makes the given InputContextCallback available for use in the future.
84 private void dispose() {
85 synchronized (InputContextCallback.class) {
86 // If sInstance is non-null, just let this object be garbage-collected
87 if (sInstance == null) {
88 // Allow any objects being held to be gc'ed
89 mTextAfterCursor = null;
90 mTextBeforeCursor = null;
91 mExtractedText = null;
97 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
100 mTextBeforeCursor = textBeforeCursor;
104 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
105 + ") in setTextBeforeCursor, ignoring.");
110 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
111 synchronized (this) {
113 mTextAfterCursor = textAfterCursor;
117 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
118 + ") in setTextAfterCursor, ignoring.");
123 public void setSelectedText(CharSequence selectedText, int seq) {
124 synchronized (this) {
126 mSelectedText = selectedText;
130 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
131 + ") in setSelectedText, ignoring.");
136 public void setCursorCapsMode(int capsMode, int seq) {
137 synchronized (this) {
139 mCursorCapsMode = capsMode;
143 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
144 + ") in setCursorCapsMode, ignoring.");
149 public void setExtractedText(ExtractedText extractedText, int seq) {
150 synchronized (this) {
152 mExtractedText = extractedText;
156 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
157 + ") in setExtractedText, ignoring.");
162 public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
163 synchronized (this) {
165 mRequestUpdateCursorAnchorInfoResult = result;
169 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
170 + ") in setCursorAnchorInfoRequestResult, ignoring.");
176 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
178 * <p>The caller must be synchronized on this callback object.
180 void waitForResultLocked() {
181 long startTime = SystemClock.uptimeMillis();
182 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
184 while (!mHaveValue) {
185 long remainingTime = endTime - SystemClock.uptimeMillis();
186 if (remainingTime <= 0) {
187 Log.w(TAG, "Timed out waiting on IInputContextCallback");
192 } catch (InterruptedException e) {
198 public InputConnectionWrapper(IInputContext inputContext,
199 @MissingMethodFlags final int missingMethods) {
200 mIInputContext = inputContext;
201 mMissingMethods = missingMethods;
204 public CharSequence getTextAfterCursor(int length, int flags) {
205 CharSequence value = null;
207 InputContextCallback callback = InputContextCallback.getInstance();
208 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
209 synchronized (callback) {
210 callback.waitForResultLocked();
211 if (callback.mHaveValue) {
212 value = callback.mTextAfterCursor;
216 } catch (RemoteException e) {
222 public CharSequence getTextBeforeCursor(int length, int flags) {
223 CharSequence value = null;
225 InputContextCallback callback = InputContextCallback.getInstance();
226 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
227 synchronized (callback) {
228 callback.waitForResultLocked();
229 if (callback.mHaveValue) {
230 value = callback.mTextBeforeCursor;
234 } catch (RemoteException e) {
240 public CharSequence getSelectedText(int flags) {
241 if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
242 // This method is not implemented.
245 CharSequence value = null;
247 InputContextCallback callback = InputContextCallback.getInstance();
248 mIInputContext.getSelectedText(flags, callback.mSeq, callback);
249 synchronized (callback) {
250 callback.waitForResultLocked();
251 if (callback.mHaveValue) {
252 value = callback.mSelectedText;
256 } catch (RemoteException e) {
262 public int getCursorCapsMode(int reqModes) {
265 InputContextCallback callback = InputContextCallback.getInstance();
266 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
267 synchronized (callback) {
268 callback.waitForResultLocked();
269 if (callback.mHaveValue) {
270 value = callback.mCursorCapsMode;
274 } catch (RemoteException e) {
280 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
281 ExtractedText value = null;
283 InputContextCallback callback = InputContextCallback.getInstance();
284 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
285 synchronized (callback) {
286 callback.waitForResultLocked();
287 if (callback.mHaveValue) {
288 value = callback.mExtractedText;
292 } catch (RemoteException e) {
298 public boolean commitText(CharSequence text, int newCursorPosition) {
300 mIInputContext.commitText(text, newCursorPosition);
302 } catch (RemoteException e) {
307 public boolean commitCompletion(CompletionInfo text) {
308 if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
309 // This method is not implemented.
313 mIInputContext.commitCompletion(text);
315 } catch (RemoteException e) {
320 public boolean commitCorrection(CorrectionInfo correctionInfo) {
322 mIInputContext.commitCorrection(correctionInfo);
324 } catch (RemoteException e) {
329 public boolean setSelection(int start, int end) {
331 mIInputContext.setSelection(start, end);
333 } catch (RemoteException e) {
338 public boolean performEditorAction(int actionCode) {
340 mIInputContext.performEditorAction(actionCode);
342 } catch (RemoteException e) {
347 public boolean performContextMenuAction(int id) {
349 mIInputContext.performContextMenuAction(id);
351 } catch (RemoteException e) {
356 public boolean setComposingRegion(int start, int end) {
357 if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
358 // This method is not implemented.
362 mIInputContext.setComposingRegion(start, end);
364 } catch (RemoteException e) {
369 public boolean setComposingText(CharSequence text, int newCursorPosition) {
371 mIInputContext.setComposingText(text, newCursorPosition);
373 } catch (RemoteException e) {
378 public boolean finishComposingText() {
380 mIInputContext.finishComposingText();
382 } catch (RemoteException e) {
387 public boolean beginBatchEdit() {
389 mIInputContext.beginBatchEdit();
391 } catch (RemoteException e) {
396 public boolean endBatchEdit() {
398 mIInputContext.endBatchEdit();
400 } catch (RemoteException e) {
405 public boolean sendKeyEvent(KeyEvent event) {
407 mIInputContext.sendKeyEvent(event);
409 } catch (RemoteException e) {
414 public boolean clearMetaKeyStates(int states) {
416 mIInputContext.clearMetaKeyStates(states);
418 } catch (RemoteException e) {
423 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
425 mIInputContext.deleteSurroundingText(beforeLength, afterLength);
427 } catch (RemoteException e) {
432 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
433 if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
434 // This method is not implemented.
438 mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
440 } catch (RemoteException e) {
445 public boolean reportFullscreenMode(boolean enabled) {
447 mIInputContext.reportFullscreenMode(enabled);
449 } catch (RemoteException e) {
454 public boolean performPrivateCommand(String action, Bundle data) {
456 mIInputContext.performPrivateCommand(action, data);
458 } catch (RemoteException e) {
463 public boolean requestCursorUpdates(int cursorUpdateMode) {
464 boolean result = false;
465 if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
466 // This method is not implemented.
470 InputContextCallback callback = InputContextCallback.getInstance();
471 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
472 synchronized (callback) {
473 callback.waitForResultLocked();
474 if (callback.mHaveValue) {
475 result = callback.mRequestUpdateCursorAnchorInfoResult;
479 } catch (RemoteException e) {
485 public Handler getHandler() {
486 // Nothing should happen when called from input method.
490 public void closeConnection() {
491 // Nothing should happen when called from input method.
494 private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
495 return (mMissingMethods & methodFlag) == methodFlag;
499 public String toString() {
500 return "InputConnectionWrapper{idHash=#"
501 + Integer.toHexString(System.identityHashCode(this))
502 + " mMissingMethods="
503 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";