blob: f9884d85d52cb7337e7cf1633545ce3efa6e735b [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.os.Bundle;
Yohei Yukawa612cce92016-02-11 17:47:33 -080020import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.os.RemoteException;
22import android.os.SystemClock;
23import android.util.Log;
24import android.view.KeyEvent;
25import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -080026import android.view.inputmethod.CorrectionInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.view.inputmethod.ExtractedText;
28import android.view.inputmethod.ExtractedTextRequest;
29import android.view.inputmethod.InputConnection;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070030import android.view.inputmethod.InputConnectionInspector;
31import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
33public class InputConnectionWrapper implements InputConnection {
34 private static final int MAX_WAIT_TIME_MILLIS = 2000;
35 private final IInputContext mIInputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070036 @MissingMethodFlags
37 private final int mMissingMethods;
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 static class InputContextCallback extends IInputContextCallback.Stub {
40 private static final String TAG = "InputConnectionWrapper.ICC";
41 public int mSeq;
42 public boolean mHaveValue;
43 public CharSequence mTextBeforeCursor;
44 public CharSequence mTextAfterCursor;
Amith Yamasania90b7f02010-08-25 18:27:20 -070045 public CharSequence mSelectedText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 public ExtractedText mExtractedText;
47 public int mCursorCapsMode;
Yohei Yukawaa277db22014-08-21 18:38:44 -070048 public boolean mRequestUpdateCursorAnchorInfoResult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049
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;
54
55 /**
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.
60 */
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) {
66 callback = sInstance;
67 sInstance = null;
68
69 // Reset the callback
70 callback.mHaveValue = false;
71 } else {
72 callback = new InputContextCallback();
73 }
74
75 // Set the sequence number
76 callback.mSeq = sSequenceNumber++;
77 return callback;
78 }
79 }
80
81 /**
82 * Makes the given InputContextCallback available for use in the future.
83 */
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;
92 sInstance = this;
93 }
94 }
95 }
96
97 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
98 synchronized (this) {
99 if (seq == mSeq) {
100 mTextBeforeCursor = textBeforeCursor;
101 mHaveValue = true;
102 notifyAll();
103 } else {
104 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
105 + ") in setTextBeforeCursor, ignoring.");
106 }
107 }
108 }
109
110 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
111 synchronized (this) {
112 if (seq == mSeq) {
113 mTextAfterCursor = textAfterCursor;
114 mHaveValue = true;
115 notifyAll();
116 } else {
117 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
118 + ") in setTextAfterCursor, ignoring.");
119 }
120 }
121 }
122
Amith Yamasania90b7f02010-08-25 18:27:20 -0700123 public void setSelectedText(CharSequence selectedText, int seq) {
124 synchronized (this) {
125 if (seq == mSeq) {
126 mSelectedText = selectedText;
127 mHaveValue = true;
128 notifyAll();
129 } else {
130 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
131 + ") in setSelectedText, ignoring.");
132 }
133 }
134 }
135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 public void setCursorCapsMode(int capsMode, int seq) {
137 synchronized (this) {
138 if (seq == mSeq) {
139 mCursorCapsMode = capsMode;
140 mHaveValue = true;
141 notifyAll();
142 } else {
143 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
144 + ") in setCursorCapsMode, ignoring.");
145 }
146 }
147 }
148
149 public void setExtractedText(ExtractedText extractedText, int seq) {
150 synchronized (this) {
151 if (seq == mSeq) {
152 mExtractedText = extractedText;
153 mHaveValue = true;
154 notifyAll();
155 } else {
156 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
157 + ") in setExtractedText, ignoring.");
158 }
159 }
160 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900161
Yohei Yukawaa277db22014-08-21 18:38:44 -0700162 public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900163 synchronized (this) {
164 if (seq == mSeq) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700165 mRequestUpdateCursorAnchorInfoResult = result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900166 mHaveValue = true;
167 notifyAll();
168 } else {
169 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
170 + ") in setCursorAnchorInfoRequestResult, ignoring.");
171 }
172 }
173 }
174
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 /**
176 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
177 *
178 * <p>The caller must be synchronized on this callback object.
179 */
180 void waitForResultLocked() {
181 long startTime = SystemClock.uptimeMillis();
182 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
183
184 while (!mHaveValue) {
185 long remainingTime = endTime - SystemClock.uptimeMillis();
186 if (remainingTime <= 0) {
187 Log.w(TAG, "Timed out waiting on IInputContextCallback");
188 return;
189 }
190 try {
191 wait(remainingTime);
192 } catch (InterruptedException e) {
193 }
194 }
195 }
196 }
197
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700198 public InputConnectionWrapper(IInputContext inputContext,
199 @MissingMethodFlags final int missingMethods) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 mIInputContext = inputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700201 mMissingMethods = missingMethods;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 }
203
204 public CharSequence getTextAfterCursor(int length, int flags) {
205 CharSequence value = null;
206 try {
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;
213 }
214 }
215 callback.dispose();
216 } catch (RemoteException e) {
217 return null;
218 }
219 return value;
220 }
221
222 public CharSequence getTextBeforeCursor(int length, int flags) {
223 CharSequence value = null;
224 try {
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;
231 }
232 }
233 callback.dispose();
234 } catch (RemoteException e) {
235 return null;
236 }
237 return value;
238 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700239
Amith Yamasania90b7f02010-08-25 18:27:20 -0700240 public CharSequence getSelectedText(int flags) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700241 if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
242 // This method is not implemented.
243 return null;
244 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700245 CharSequence value = null;
246 try {
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;
253 }
254 }
255 callback.dispose();
256 } catch (RemoteException e) {
257 return null;
258 }
259 return value;
260 }
261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 public int getCursorCapsMode(int reqModes) {
263 int value = 0;
264 try {
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;
271 }
272 }
273 callback.dispose();
274 } catch (RemoteException e) {
275 return 0;
276 }
277 return value;
278 }
satoke3797a12011-03-22 06:34:48 +0900279
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
281 ExtractedText value = null;
282 try {
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;
289 }
290 }
291 callback.dispose();
292 } catch (RemoteException e) {
293 return null;
294 }
295 return value;
296 }
297
298 public boolean commitText(CharSequence text, int newCursorPosition) {
299 try {
300 mIInputContext.commitText(text, newCursorPosition);
301 return true;
302 } catch (RemoteException e) {
303 return false;
304 }
305 }
306
307 public boolean commitCompletion(CompletionInfo text) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700308 if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
309 // This method is not implemented.
310 return false;
311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 try {
313 mIInputContext.commitCompletion(text);
314 return true;
315 } catch (RemoteException e) {
316 return false;
317 }
318 }
319
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800320 public boolean commitCorrection(CorrectionInfo correctionInfo) {
321 try {
322 mIInputContext.commitCorrection(correctionInfo);
323 return true;
324 } catch (RemoteException e) {
325 return false;
326 }
327 }
328
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 public boolean setSelection(int start, int end) {
330 try {
331 mIInputContext.setSelection(start, end);
332 return true;
333 } catch (RemoteException e) {
334 return false;
335 }
336 }
337
338 public boolean performEditorAction(int actionCode) {
339 try {
340 mIInputContext.performEditorAction(actionCode);
341 return true;
342 } catch (RemoteException e) {
343 return false;
344 }
345 }
346
347 public boolean performContextMenuAction(int id) {
348 try {
349 mIInputContext.performContextMenuAction(id);
350 return true;
351 } catch (RemoteException e) {
352 return false;
353 }
354 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700355
356 public boolean setComposingRegion(int start, int end) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700357 if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
358 // This method is not implemented.
359 return false;
360 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700361 try {
362 mIInputContext.setComposingRegion(start, end);
363 return true;
364 } catch (RemoteException e) {
365 return false;
366 }
367 }
368
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 public boolean setComposingText(CharSequence text, int newCursorPosition) {
370 try {
371 mIInputContext.setComposingText(text, newCursorPosition);
372 return true;
373 } catch (RemoteException e) {
374 return false;
375 }
376 }
377
378 public boolean finishComposingText() {
379 try {
380 mIInputContext.finishComposingText();
381 return true;
382 } catch (RemoteException e) {
383 return false;
384 }
385 }
386
387 public boolean beginBatchEdit() {
388 try {
389 mIInputContext.beginBatchEdit();
390 return true;
391 } catch (RemoteException e) {
392 return false;
393 }
394 }
395
396 public boolean endBatchEdit() {
397 try {
398 mIInputContext.endBatchEdit();
399 return true;
400 } catch (RemoteException e) {
401 return false;
402 }
403 }
404
405 public boolean sendKeyEvent(KeyEvent event) {
406 try {
407 mIInputContext.sendKeyEvent(event);
408 return true;
409 } catch (RemoteException e) {
410 return false;
411 }
412 }
413
414 public boolean clearMetaKeyStates(int states) {
415 try {
416 mIInputContext.clearMetaKeyStates(states);
417 return true;
418 } catch (RemoteException e) {
419 return false;
420 }
421 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800422
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800423 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 try {
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800425 mIInputContext.deleteSurroundingText(beforeLength, afterLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 return true;
427 } catch (RemoteException e) {
428 return false;
429 }
430 }
431
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800432 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700433 if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
434 // This method is not implemented.
435 return false;
436 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800437 try {
438 mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
439 return true;
440 } catch (RemoteException e) {
441 return false;
442 }
443 }
444
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 public boolean reportFullscreenMode(boolean enabled) {
446 try {
447 mIInputContext.reportFullscreenMode(enabled);
448 return true;
449 } catch (RemoteException e) {
450 return false;
451 }
452 }
453
454 public boolean performPrivateCommand(String action, Bundle data) {
455 try {
456 mIInputContext.performPrivateCommand(action, data);
457 return true;
458 } catch (RemoteException e) {
459 return false;
460 }
461 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900462
Yohei Yukawad8636ea2014-09-02 22:03:30 -0700463 public boolean requestCursorUpdates(int cursorUpdateMode) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700464 boolean result = false;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700465 if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
466 // This method is not implemented.
467 return false;
468 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900469 try {
470 InputContextCallback callback = InputContextCallback.getInstance();
Yohei Yukawaa277db22014-08-21 18:38:44 -0700471 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900472 synchronized (callback) {
473 callback.waitForResultLocked();
474 if (callback.mHaveValue) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700475 result = callback.mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900476 }
477 }
478 callback.dispose();
479 } catch (RemoteException e) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700480 return false;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900481 }
Yohei Yukawaa277db22014-08-21 18:38:44 -0700482 return result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900483 }
Yohei Yukawa612cce92016-02-11 17:47:33 -0800484
485 public Handler getHandler() {
486 // Nothing should happen when called from input method.
487 return null;
488 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700489
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700490 public void closeConnection() {
491 // Nothing should happen when called from input method.
492 }
493
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700494 private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
495 return (mMissingMethods & methodFlag) == methodFlag;
496 }
497
498 @Override
499 public String toString() {
500 return "InputConnectionWrapper{idHash=#"
501 + Integer.toHexString(System.identityHashCode(this))
502 + " mMissingMethods="
503 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";
504 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505}