blob: 3c44e58a9467b4dac8d10d99d7d53741a39e953e [file] [log] [blame]
Kenny Root15a4d2f2010-03-11 18:20:12 -08001/*
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080017package com.android.internal.view;
18
19import com.android.internal.view.IInputContext;
20
21import android.os.Bundle;
22import android.os.RemoteException;
23import android.os.SystemClock;
24import android.util.Log;
25import android.view.KeyEvent;
26import android.view.inputmethod.CompletionInfo;
27import android.view.inputmethod.ExtractedText;
28import android.view.inputmethod.ExtractedTextRequest;
29import android.view.inputmethod.InputConnection;
30
31public class InputConnectionWrapper implements InputConnection {
32 private static final int MAX_WAIT_TIME_MILLIS = 2000;
33 private final IInputContext mIInputContext;
34
35 static class InputContextCallback extends IInputContextCallback.Stub {
36 private static final String TAG = "InputConnectionWrapper.ICC";
37 public int mSeq;
38 public boolean mHaveValue;
39 public CharSequence mTextBeforeCursor;
40 public CharSequence mTextAfterCursor;
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 setCursorCapsMode(int capsMode, int seq) {
118 synchronized (this) {
119 if (seq == mSeq) {
120 mCursorCapsMode = capsMode;
121 mHaveValue = true;
122 notifyAll();
123 } else {
124 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
125 + ") in setCursorCapsMode, ignoring.");
126 }
127 }
128 }
129
130 public void setExtractedText(ExtractedText extractedText, int seq) {
131 synchronized (this) {
132 if (seq == mSeq) {
133 mExtractedText = extractedText;
134 mHaveValue = true;
135 notifyAll();
136 } else {
137 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
138 + ") in setExtractedText, ignoring.");
139 }
140 }
141 }
142
143 /**
144 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
145 *
146 * <p>The caller must be synchronized on this callback object.
147 */
148 void waitForResultLocked() {
149 long startTime = SystemClock.uptimeMillis();
150 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
151
152 while (!mHaveValue) {
153 long remainingTime = endTime - SystemClock.uptimeMillis();
154 if (remainingTime <= 0) {
155 Log.w(TAG, "Timed out waiting on IInputContextCallback");
156 return;
157 }
158 try {
159 wait(remainingTime);
160 } catch (InterruptedException e) {
161 }
162 }
163 }
164 }
165
166 public InputConnectionWrapper(IInputContext inputContext) {
167 mIInputContext = inputContext;
168 }
169
170 public CharSequence getTextAfterCursor(int length, int flags) {
171 CharSequence value = null;
172 try {
173 InputContextCallback callback = InputContextCallback.getInstance();
174 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
175 synchronized (callback) {
176 callback.waitForResultLocked();
177 if (callback.mHaveValue) {
178 value = callback.mTextAfterCursor;
179 }
180 }
181 callback.dispose();
182 } catch (RemoteException e) {
183 return null;
184 }
185 return value;
186 }
187
188 public CharSequence getTextBeforeCursor(int length, int flags) {
189 CharSequence value = null;
190 try {
191 InputContextCallback callback = InputContextCallback.getInstance();
192 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
193 synchronized (callback) {
194 callback.waitForResultLocked();
195 if (callback.mHaveValue) {
196 value = callback.mTextBeforeCursor;
197 }
198 }
199 callback.dispose();
200 } catch (RemoteException e) {
201 return null;
202 }
203 return value;
204 }
205
206 public int getCursorCapsMode(int reqModes) {
207 int value = 0;
208 try {
209 InputContextCallback callback = InputContextCallback.getInstance();
210 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
211 synchronized (callback) {
212 callback.waitForResultLocked();
213 if (callback.mHaveValue) {
214 value = callback.mCursorCapsMode;
215 }
216 }
217 callback.dispose();
218 } catch (RemoteException e) {
219 return 0;
220 }
221 return value;
222 }
223
224 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
225 ExtractedText value = null;
226 try {
227 InputContextCallback callback = InputContextCallback.getInstance();
228 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
229 synchronized (callback) {
230 callback.waitForResultLocked();
231 if (callback.mHaveValue) {
232 value = callback.mExtractedText;
233 }
234 }
235 callback.dispose();
236 } catch (RemoteException e) {
237 return null;
238 }
239 return value;
240 }
241
242 public boolean commitText(CharSequence text, int newCursorPosition) {
243 try {
244 mIInputContext.commitText(text, newCursorPosition);
245 return true;
246 } catch (RemoteException e) {
247 return false;
248 }
249 }
250
251 public boolean commitCompletion(CompletionInfo text) {
252 try {
253 mIInputContext.commitCompletion(text);
254 return true;
255 } catch (RemoteException e) {
256 return false;
257 }
258 }
259
260 public boolean setSelection(int start, int end) {
261 try {
262 mIInputContext.setSelection(start, end);
263 return true;
264 } catch (RemoteException e) {
265 return false;
266 }
267 }
268
269 public boolean performEditorAction(int actionCode) {
270 try {
271 mIInputContext.performEditorAction(actionCode);
272 return true;
273 } catch (RemoteException e) {
274 return false;
275 }
276 }
277
278 public boolean performContextMenuAction(int id) {
279 try {
280 mIInputContext.performContextMenuAction(id);
281 return true;
282 } catch (RemoteException e) {
283 return false;
284 }
285 }
286
287 public boolean setComposingText(CharSequence text, int newCursorPosition) {
288 try {
289 mIInputContext.setComposingText(text, newCursorPosition);
290 return true;
291 } catch (RemoteException e) {
292 return false;
293 }
294 }
295
296 public boolean finishComposingText() {
297 try {
298 mIInputContext.finishComposingText();
299 return true;
300 } catch (RemoteException e) {
301 return false;
302 }
303 }
304
305 public boolean beginBatchEdit() {
306 try {
307 mIInputContext.beginBatchEdit();
308 return true;
309 } catch (RemoteException e) {
310 return false;
311 }
312 }
313
314 public boolean endBatchEdit() {
315 try {
316 mIInputContext.endBatchEdit();
317 return true;
318 } catch (RemoteException e) {
319 return false;
320 }
321 }
322
323 public boolean sendKeyEvent(KeyEvent event) {
324 try {
325 mIInputContext.sendKeyEvent(event);
326 return true;
327 } catch (RemoteException e) {
328 return false;
329 }
330 }
331
332 public boolean clearMetaKeyStates(int states) {
333 try {
334 mIInputContext.clearMetaKeyStates(states);
335 return true;
336 } catch (RemoteException e) {
337 return false;
338 }
339 }
340
341 public boolean deleteSurroundingText(int leftLength, int rightLength) {
342 try {
343 mIInputContext.deleteSurroundingText(leftLength, rightLength);
344 return true;
345 } catch (RemoteException e) {
346 return false;
347 }
348 }
349
350 public boolean reportFullscreenMode(boolean enabled) {
351 try {
352 mIInputContext.reportFullscreenMode(enabled);
353 return true;
354 } catch (RemoteException e) {
355 return false;
356 }
357 }
358
359 public boolean performPrivateCommand(String action, Bundle data) {
360 try {
361 mIInputContext.performPrivateCommand(action, data);
362 return true;
363 } catch (RemoteException e) {
364 return false;
365 }
366 }
367}