blob: 5e78ec5e9833050086772c414fab65a8f24d0d3d [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;
Yohei Yukawa152944f2016-06-10 19:04:34 -070032import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
34public class InputConnectionWrapper implements InputConnection {
35 private static final int MAX_WAIT_TIME_MILLIS = 2000;
36 private final IInputContext mIInputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070037 @MissingMethodFlags
38 private final int mMissingMethods;
39
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 static class InputContextCallback extends IInputContextCallback.Stub {
41 private static final String TAG = "InputConnectionWrapper.ICC";
42 public int mSeq;
43 public boolean mHaveValue;
44 public CharSequence mTextBeforeCursor;
45 public CharSequence mTextAfterCursor;
Amith Yamasania90b7f02010-08-25 18:27:20 -070046 public CharSequence mSelectedText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 public ExtractedText mExtractedText;
48 public int mCursorCapsMode;
Yohei Yukawaa277db22014-08-21 18:38:44 -070049 public boolean mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawaadebb522016-06-17 10:10:39 -070050 public boolean mCommitContentResult;
Yohei Yukawa152944f2016-06-10 19:04:34 -070051
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
53 // exclusive access to this object.
54 private static InputContextCallback sInstance = new InputContextCallback();
55 private static int sSequenceNumber = 1;
56
57 /**
58 * Returns an InputContextCallback object that is guaranteed not to be in use by
59 * any other thread. The returned object's 'have value' flag is cleared and its expected
60 * sequence number is set to a new integer. We use a sequence number so that replies that
61 * occur after a timeout has expired are not interpreted as replies to a later request.
62 */
63 private static InputContextCallback getInstance() {
64 synchronized (InputContextCallback.class) {
65 // Return sInstance if it's non-null, otherwise construct a new callback
66 InputContextCallback callback;
67 if (sInstance != null) {
68 callback = sInstance;
69 sInstance = null;
70
71 // Reset the callback
72 callback.mHaveValue = false;
73 } else {
74 callback = new InputContextCallback();
75 }
76
77 // Set the sequence number
78 callback.mSeq = sSequenceNumber++;
79 return callback;
80 }
81 }
82
83 /**
84 * Makes the given InputContextCallback available for use in the future.
85 */
86 private void dispose() {
87 synchronized (InputContextCallback.class) {
88 // If sInstance is non-null, just let this object be garbage-collected
89 if (sInstance == null) {
90 // Allow any objects being held to be gc'ed
91 mTextAfterCursor = null;
92 mTextBeforeCursor = null;
93 mExtractedText = null;
94 sInstance = this;
95 }
96 }
97 }
98
99 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
100 synchronized (this) {
101 if (seq == mSeq) {
102 mTextBeforeCursor = textBeforeCursor;
103 mHaveValue = true;
104 notifyAll();
105 } else {
106 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
107 + ") in setTextBeforeCursor, ignoring.");
108 }
109 }
110 }
111
112 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
113 synchronized (this) {
114 if (seq == mSeq) {
115 mTextAfterCursor = textAfterCursor;
116 mHaveValue = true;
117 notifyAll();
118 } else {
119 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
120 + ") in setTextAfterCursor, ignoring.");
121 }
122 }
123 }
124
Amith Yamasania90b7f02010-08-25 18:27:20 -0700125 public void setSelectedText(CharSequence selectedText, int seq) {
126 synchronized (this) {
127 if (seq == mSeq) {
128 mSelectedText = selectedText;
129 mHaveValue = true;
130 notifyAll();
131 } else {
132 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
133 + ") in setSelectedText, ignoring.");
134 }
135 }
136 }
137
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 public void setCursorCapsMode(int capsMode, int seq) {
139 synchronized (this) {
140 if (seq == mSeq) {
141 mCursorCapsMode = capsMode;
142 mHaveValue = true;
143 notifyAll();
144 } else {
145 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
146 + ") in setCursorCapsMode, ignoring.");
147 }
148 }
149 }
150
151 public void setExtractedText(ExtractedText extractedText, int seq) {
152 synchronized (this) {
153 if (seq == mSeq) {
154 mExtractedText = extractedText;
155 mHaveValue = true;
156 notifyAll();
157 } else {
158 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
159 + ") in setExtractedText, ignoring.");
160 }
161 }
162 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900163
Yohei Yukawaa277db22014-08-21 18:38:44 -0700164 public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900165 synchronized (this) {
166 if (seq == mSeq) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700167 mRequestUpdateCursorAnchorInfoResult = result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900168 mHaveValue = true;
169 notifyAll();
170 } else {
171 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
172 + ") in setCursorAnchorInfoRequestResult, ignoring.");
173 }
174 }
175 }
176
Yohei Yukawaadebb522016-06-17 10:10:39 -0700177 public void setCommitContentResult(boolean result, int seq) {
Yohei Yukawa152944f2016-06-10 19:04:34 -0700178 synchronized (this) {
179 if (seq == mSeq) {
Yohei Yukawaadebb522016-06-17 10:10:39 -0700180 mCommitContentResult = result;
Yohei Yukawa152944f2016-06-10 19:04:34 -0700181 mHaveValue = true;
182 notifyAll();
183 } else {
184 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
Yohei Yukawaadebb522016-06-17 10:10:39 -0700185 + ") in setCommitContentResult, ignoring.");
Yohei Yukawa152944f2016-06-10 19:04:34 -0700186 }
187 }
188 }
189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 /**
191 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
192 *
193 * <p>The caller must be synchronized on this callback object.
194 */
195 void waitForResultLocked() {
196 long startTime = SystemClock.uptimeMillis();
197 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
198
199 while (!mHaveValue) {
200 long remainingTime = endTime - SystemClock.uptimeMillis();
201 if (remainingTime <= 0) {
202 Log.w(TAG, "Timed out waiting on IInputContextCallback");
203 return;
204 }
205 try {
206 wait(remainingTime);
207 } catch (InterruptedException e) {
208 }
209 }
210 }
211 }
212
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700213 public InputConnectionWrapper(IInputContext inputContext,
214 @MissingMethodFlags final int missingMethods) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 mIInputContext = inputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700216 mMissingMethods = missingMethods;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
218
219 public CharSequence getTextAfterCursor(int length, int flags) {
220 CharSequence value = null;
221 try {
222 InputContextCallback callback = InputContextCallback.getInstance();
223 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
224 synchronized (callback) {
225 callback.waitForResultLocked();
226 if (callback.mHaveValue) {
227 value = callback.mTextAfterCursor;
228 }
229 }
230 callback.dispose();
231 } catch (RemoteException e) {
232 return null;
233 }
234 return value;
235 }
236
237 public CharSequence getTextBeforeCursor(int length, int flags) {
238 CharSequence value = null;
239 try {
240 InputContextCallback callback = InputContextCallback.getInstance();
241 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
242 synchronized (callback) {
243 callback.waitForResultLocked();
244 if (callback.mHaveValue) {
245 value = callback.mTextBeforeCursor;
246 }
247 }
248 callback.dispose();
249 } catch (RemoteException e) {
250 return null;
251 }
252 return value;
253 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700254
Amith Yamasania90b7f02010-08-25 18:27:20 -0700255 public CharSequence getSelectedText(int flags) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700256 if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
257 // This method is not implemented.
258 return null;
259 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700260 CharSequence value = null;
261 try {
262 InputContextCallback callback = InputContextCallback.getInstance();
263 mIInputContext.getSelectedText(flags, callback.mSeq, callback);
264 synchronized (callback) {
265 callback.waitForResultLocked();
266 if (callback.mHaveValue) {
267 value = callback.mSelectedText;
268 }
269 }
270 callback.dispose();
271 } catch (RemoteException e) {
272 return null;
273 }
274 return value;
275 }
276
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 public int getCursorCapsMode(int reqModes) {
278 int value = 0;
279 try {
280 InputContextCallback callback = InputContextCallback.getInstance();
281 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
282 synchronized (callback) {
283 callback.waitForResultLocked();
284 if (callback.mHaveValue) {
285 value = callback.mCursorCapsMode;
286 }
287 }
288 callback.dispose();
289 } catch (RemoteException e) {
290 return 0;
291 }
292 return value;
293 }
satoke3797a12011-03-22 06:34:48 +0900294
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
296 ExtractedText value = null;
297 try {
298 InputContextCallback callback = InputContextCallback.getInstance();
299 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
300 synchronized (callback) {
301 callback.waitForResultLocked();
302 if (callback.mHaveValue) {
303 value = callback.mExtractedText;
304 }
305 }
306 callback.dispose();
307 } catch (RemoteException e) {
308 return null;
309 }
310 return value;
311 }
312
313 public boolean commitText(CharSequence text, int newCursorPosition) {
314 try {
315 mIInputContext.commitText(text, newCursorPosition);
316 return true;
317 } catch (RemoteException e) {
318 return false;
319 }
320 }
321
322 public boolean commitCompletion(CompletionInfo text) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700323 if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
324 // This method is not implemented.
325 return false;
326 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 try {
328 mIInputContext.commitCompletion(text);
329 return true;
330 } catch (RemoteException e) {
331 return false;
332 }
333 }
334
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800335 public boolean commitCorrection(CorrectionInfo correctionInfo) {
336 try {
337 mIInputContext.commitCorrection(correctionInfo);
338 return true;
339 } catch (RemoteException e) {
340 return false;
341 }
342 }
343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 public boolean setSelection(int start, int end) {
345 try {
346 mIInputContext.setSelection(start, end);
347 return true;
348 } catch (RemoteException e) {
349 return false;
350 }
351 }
352
353 public boolean performEditorAction(int actionCode) {
354 try {
355 mIInputContext.performEditorAction(actionCode);
356 return true;
357 } catch (RemoteException e) {
358 return false;
359 }
360 }
361
362 public boolean performContextMenuAction(int id) {
363 try {
364 mIInputContext.performContextMenuAction(id);
365 return true;
366 } catch (RemoteException e) {
367 return false;
368 }
369 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700370
371 public boolean setComposingRegion(int start, int end) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700372 if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
373 // This method is not implemented.
374 return false;
375 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700376 try {
377 mIInputContext.setComposingRegion(start, end);
378 return true;
379 } catch (RemoteException e) {
380 return false;
381 }
382 }
383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 public boolean setComposingText(CharSequence text, int newCursorPosition) {
385 try {
386 mIInputContext.setComposingText(text, newCursorPosition);
387 return true;
388 } catch (RemoteException e) {
389 return false;
390 }
391 }
392
393 public boolean finishComposingText() {
394 try {
395 mIInputContext.finishComposingText();
396 return true;
397 } catch (RemoteException e) {
398 return false;
399 }
400 }
401
402 public boolean beginBatchEdit() {
403 try {
404 mIInputContext.beginBatchEdit();
405 return true;
406 } catch (RemoteException e) {
407 return false;
408 }
409 }
410
411 public boolean endBatchEdit() {
412 try {
413 mIInputContext.endBatchEdit();
414 return true;
415 } catch (RemoteException e) {
416 return false;
417 }
418 }
419
420 public boolean sendKeyEvent(KeyEvent event) {
421 try {
422 mIInputContext.sendKeyEvent(event);
423 return true;
424 } catch (RemoteException e) {
425 return false;
426 }
427 }
428
429 public boolean clearMetaKeyStates(int states) {
430 try {
431 mIInputContext.clearMetaKeyStates(states);
432 return true;
433 } catch (RemoteException e) {
434 return false;
435 }
436 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800437
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800438 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 try {
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800440 mIInputContext.deleteSurroundingText(beforeLength, afterLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 return true;
442 } catch (RemoteException e) {
443 return false;
444 }
445 }
446
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800447 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700448 if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
449 // This method is not implemented.
450 return false;
451 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800452 try {
453 mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
454 return true;
455 } catch (RemoteException e) {
456 return false;
457 }
458 }
459
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 public boolean reportFullscreenMode(boolean enabled) {
461 try {
462 mIInputContext.reportFullscreenMode(enabled);
463 return true;
464 } catch (RemoteException e) {
465 return false;
466 }
467 }
468
469 public boolean performPrivateCommand(String action, Bundle data) {
470 try {
471 mIInputContext.performPrivateCommand(action, data);
472 return true;
473 } catch (RemoteException e) {
474 return false;
475 }
476 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900477
Yohei Yukawad8636ea2014-09-02 22:03:30 -0700478 public boolean requestCursorUpdates(int cursorUpdateMode) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700479 boolean result = false;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700480 if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
481 // This method is not implemented.
482 return false;
483 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900484 try {
485 InputContextCallback callback = InputContextCallback.getInstance();
Yohei Yukawaa277db22014-08-21 18:38:44 -0700486 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900487 synchronized (callback) {
488 callback.waitForResultLocked();
489 if (callback.mHaveValue) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700490 result = callback.mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900491 }
492 }
493 callback.dispose();
494 } catch (RemoteException e) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700495 return false;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900496 }
Yohei Yukawaa277db22014-08-21 18:38:44 -0700497 return result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900498 }
Yohei Yukawa612cce92016-02-11 17:47:33 -0800499
500 public Handler getHandler() {
501 // Nothing should happen when called from input method.
502 return null;
503 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700504
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700505 public void closeConnection() {
506 // Nothing should happen when called from input method.
507 }
508
Yohei Yukawaadebb522016-06-17 10:10:39 -0700509 public boolean commitContent(InputContentInfo inputContentInfo, Bundle opts) {
Yohei Yukawa152944f2016-06-10 19:04:34 -0700510 boolean result = false;
Yohei Yukawaadebb522016-06-17 10:10:39 -0700511 if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
Yohei Yukawa152944f2016-06-10 19:04:34 -0700512 // This method is not implemented.
513 return false;
514 }
515 try {
516 InputContextCallback callback = InputContextCallback.getInstance();
Yohei Yukawaadebb522016-06-17 10:10:39 -0700517 mIInputContext.commitContent(inputContentInfo, opts, callback.mSeq, callback);
Yohei Yukawa152944f2016-06-10 19:04:34 -0700518 synchronized (callback) {
519 callback.waitForResultLocked();
520 if (callback.mHaveValue) {
Yohei Yukawaadebb522016-06-17 10:10:39 -0700521 result = callback.mCommitContentResult;
Yohei Yukawa152944f2016-06-10 19:04:34 -0700522 }
523 }
524 callback.dispose();
525 } catch (RemoteException e) {
526 return false;
527 }
528 return result;
529 }
530
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700531 private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
532 return (mMissingMethods & methodFlag) == methodFlag;
533 }
534
535 @Override
536 public String toString() {
537 return "InputConnectionWrapper{idHash=#"
538 + Integer.toHexString(System.identityHashCode(this))
539 + " mMissingMethods="
540 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";
541 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542}