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.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.ExtractedText;
26 import android.view.inputmethod.ExtractedTextRequest;
27 import android.view.inputmethod.InputConnection;
29 public class InputConnectionWrapper implements InputConnection {
30 private static final int MAX_WAIT_TIME_MILLIS = 2000;
31 private final IInputContext mIInputContext;
33 static class InputContextCallback extends IInputContextCallback.Stub {
34 private static final String TAG = "InputConnectionWrapper.ICC";
36 public boolean mHaveValue;
37 public CharSequence mTextBeforeCursor;
38 public CharSequence mTextAfterCursor;
39 public CharSequence mSelectedText;
40 public ExtractedText mExtractedText;
41 public int mCursorCapsMode;
43 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
44 // exclusive access to this object.
45 private static InputContextCallback sInstance = new InputContextCallback();
46 private static int sSequenceNumber = 1;
49 * Returns an InputContextCallback object that is guaranteed not to be in use by
50 * any other thread. The returned object's 'have value' flag is cleared and its expected
51 * sequence number is set to a new integer. We use a sequence number so that replies that
52 * occur after a timeout has expired are not interpreted as replies to a later request.
54 private static InputContextCallback getInstance() {
55 synchronized (InputContextCallback.class) {
56 // Return sInstance if it's non-null, otherwise construct a new callback
57 InputContextCallback callback;
58 if (sInstance != null) {
63 callback.mHaveValue = false;
65 callback = new InputContextCallback();
68 // Set the sequence number
69 callback.mSeq = sSequenceNumber++;
75 * Makes the given InputContextCallback available for use in the future.
77 private void dispose() {
78 synchronized (InputContextCallback.class) {
79 // If sInstance is non-null, just let this object be garbage-collected
80 if (sInstance == null) {
81 // Allow any objects being held to be gc'ed
82 mTextAfterCursor = null;
83 mTextBeforeCursor = null;
84 mExtractedText = null;
90 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
93 mTextBeforeCursor = textBeforeCursor;
97 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
98 + ") in setTextBeforeCursor, ignoring.");
103 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
104 synchronized (this) {
106 mTextAfterCursor = textAfterCursor;
110 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
111 + ") in setTextAfterCursor, ignoring.");
116 public void setSelectedText(CharSequence selectedText, int seq) {
117 synchronized (this) {
119 mSelectedText = selectedText;
123 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
124 + ") in setSelectedText, ignoring.");
129 public void setCursorCapsMode(int capsMode, int seq) {
130 synchronized (this) {
132 mCursorCapsMode = capsMode;
136 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
137 + ") in setCursorCapsMode, ignoring.");
142 public void setExtractedText(ExtractedText extractedText, int seq) {
143 synchronized (this) {
145 mExtractedText = extractedText;
149 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
150 + ") in setExtractedText, ignoring.");
156 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
158 * <p>The caller must be synchronized on this callback object.
160 void waitForResultLocked() {
161 long startTime = SystemClock.uptimeMillis();
162 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
164 while (!mHaveValue) {
165 long remainingTime = endTime - SystemClock.uptimeMillis();
166 if (remainingTime <= 0) {
167 Log.w(TAG, "Timed out waiting on IInputContextCallback");
172 } catch (InterruptedException e) {
178 public InputConnectionWrapper(IInputContext inputContext) {
179 mIInputContext = inputContext;
182 public CharSequence getTextAfterCursor(int length, int flags) {
183 CharSequence value = null;
185 InputContextCallback callback = InputContextCallback.getInstance();
186 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
187 synchronized (callback) {
188 callback.waitForResultLocked();
189 if (callback.mHaveValue) {
190 value = callback.mTextAfterCursor;
194 } catch (RemoteException e) {
200 public CharSequence getTextBeforeCursor(int length, int flags) {
201 CharSequence value = null;
203 InputContextCallback callback = InputContextCallback.getInstance();
204 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
205 synchronized (callback) {
206 callback.waitForResultLocked();
207 if (callback.mHaveValue) {
208 value = callback.mTextBeforeCursor;
212 } catch (RemoteException e) {
218 public CharSequence getSelectedText(int flags) {
219 CharSequence value = null;
221 InputContextCallback callback = InputContextCallback.getInstance();
222 mIInputContext.getSelectedText(flags, callback.mSeq, callback);
223 synchronized (callback) {
224 callback.waitForResultLocked();
225 if (callback.mHaveValue) {
226 value = callback.mSelectedText;
230 } catch (RemoteException e) {
236 public int getCursorCapsMode(int reqModes) {
239 InputContextCallback callback = InputContextCallback.getInstance();
240 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
241 synchronized (callback) {
242 callback.waitForResultLocked();
243 if (callback.mHaveValue) {
244 value = callback.mCursorCapsMode;
248 } catch (RemoteException e) {
254 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
255 ExtractedText value = null;
257 InputContextCallback callback = InputContextCallback.getInstance();
258 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
259 synchronized (callback) {
260 callback.waitForResultLocked();
261 if (callback.mHaveValue) {
262 value = callback.mExtractedText;
266 } catch (RemoteException e) {
272 public boolean commitText(CharSequence text, int newCursorPosition) {
274 mIInputContext.commitText(text, newCursorPosition);
276 } catch (RemoteException e) {
281 public boolean commitCompletion(CompletionInfo text) {
283 mIInputContext.commitCompletion(text);
285 } catch (RemoteException e) {
290 public boolean setSelection(int start, int end) {
292 mIInputContext.setSelection(start, end);
294 } catch (RemoteException e) {
299 public boolean performEditorAction(int actionCode) {
301 mIInputContext.performEditorAction(actionCode);
303 } catch (RemoteException e) {
308 public boolean performContextMenuAction(int id) {
310 mIInputContext.performContextMenuAction(id);
312 } catch (RemoteException e) {
317 public boolean setComposingRegion(int start, int end) {
319 mIInputContext.setComposingRegion(start, end);
321 } catch (RemoteException e) {
326 public boolean setComposingText(CharSequence text, int newCursorPosition) {
328 mIInputContext.setComposingText(text, newCursorPosition);
330 } catch (RemoteException e) {
335 public boolean finishComposingText() {
337 mIInputContext.finishComposingText();
339 } catch (RemoteException e) {
344 public boolean beginBatchEdit() {
346 mIInputContext.beginBatchEdit();
348 } catch (RemoteException e) {
353 public boolean endBatchEdit() {
355 mIInputContext.endBatchEdit();
357 } catch (RemoteException e) {
362 public boolean sendKeyEvent(KeyEvent event) {
364 mIInputContext.sendKeyEvent(event);
366 } catch (RemoteException e) {
371 public boolean clearMetaKeyStates(int states) {
373 mIInputContext.clearMetaKeyStates(states);
375 } catch (RemoteException e) {
380 public boolean deleteSurroundingText(int leftLength, int rightLength) {
382 mIInputContext.deleteSurroundingText(leftLength, rightLength);
384 } catch (RemoteException e) {
389 public boolean reportFullscreenMode(boolean enabled) {
391 mIInputContext.reportFullscreenMode(enabled);
393 } catch (RemoteException e) {
398 public boolean performPrivateCommand(String action, Bundle data) {
400 mIInputContext.performPrivateCommand(action, data);
402 } catch (RemoteException e) {