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