blob: 6d4d45c7041f1f96ca7358efc8921f533cc2f8be [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
Yohei Yukawa152944f2016-06-10 19:04:34 -070019import android.content.ClipData;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.os.Bundle;
Yohei Yukawa612cce92016-02-11 17:47:33 -080021import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.os.RemoteException;
23import android.os.SystemClock;
24import android.util.Log;
25import android.view.KeyEvent;
26import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -080027import android.view.inputmethod.CorrectionInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.inputmethod.ExtractedText;
29import android.view.inputmethod.ExtractedTextRequest;
30import android.view.inputmethod.InputConnection;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070031import android.view.inputmethod.InputConnectionInspector;
32import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
Yohei Yukawa152944f2016-06-10 19:04:34 -070033import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034
35public class InputConnectionWrapper implements InputConnection {
36 private static final int MAX_WAIT_TIME_MILLIS = 2000;
37 private final IInputContext mIInputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070038 @MissingMethodFlags
39 private final int mMissingMethods;
40
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 static class InputContextCallback extends IInputContextCallback.Stub {
42 private static final String TAG = "InputConnectionWrapper.ICC";
43 public int mSeq;
44 public boolean mHaveValue;
45 public CharSequence mTextBeforeCursor;
46 public CharSequence mTextAfterCursor;
Amith Yamasania90b7f02010-08-25 18:27:20 -070047 public CharSequence mSelectedText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 public ExtractedText mExtractedText;
49 public int mCursorCapsMode;
Yohei Yukawaa277db22014-08-21 18:38:44 -070050 public boolean mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawa152944f2016-06-10 19:04:34 -070051 public boolean mInsertContentResult;
52
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
54 // exclusive access to this object.
55 private static InputContextCallback sInstance = new InputContextCallback();
56 private static int sSequenceNumber = 1;
57
58 /**
59 * Returns an InputContextCallback object that is guaranteed not to be in use by
60 * any other thread. The returned object's 'have value' flag is cleared and its expected
61 * sequence number is set to a new integer. We use a sequence number so that replies that
62 * occur after a timeout has expired are not interpreted as replies to a later request.
63 */
64 private static InputContextCallback getInstance() {
65 synchronized (InputContextCallback.class) {
66 // Return sInstance if it's non-null, otherwise construct a new callback
67 InputContextCallback callback;
68 if (sInstance != null) {
69 callback = sInstance;
70 sInstance = null;
71
72 // Reset the callback
73 callback.mHaveValue = false;
74 } else {
75 callback = new InputContextCallback();
76 }
77
78 // Set the sequence number
79 callback.mSeq = sSequenceNumber++;
80 return callback;
81 }
82 }
83
84 /**
85 * Makes the given InputContextCallback available for use in the future.
86 */
87 private void dispose() {
88 synchronized (InputContextCallback.class) {
89 // If sInstance is non-null, just let this object be garbage-collected
90 if (sInstance == null) {
91 // Allow any objects being held to be gc'ed
92 mTextAfterCursor = null;
93 mTextBeforeCursor = null;
94 mExtractedText = null;
95 sInstance = this;
96 }
97 }
98 }
99
100 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
101 synchronized (this) {
102 if (seq == mSeq) {
103 mTextBeforeCursor = textBeforeCursor;
104 mHaveValue = true;
105 notifyAll();
106 } else {
107 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
108 + ") in setTextBeforeCursor, ignoring.");
109 }
110 }
111 }
112
113 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
114 synchronized (this) {
115 if (seq == mSeq) {
116 mTextAfterCursor = textAfterCursor;
117 mHaveValue = true;
118 notifyAll();
119 } else {
120 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
121 + ") in setTextAfterCursor, ignoring.");
122 }
123 }
124 }
125
Amith Yamasania90b7f02010-08-25 18:27:20 -0700126 public void setSelectedText(CharSequence selectedText, int seq) {
127 synchronized (this) {
128 if (seq == mSeq) {
129 mSelectedText = selectedText;
130 mHaveValue = true;
131 notifyAll();
132 } else {
133 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
134 + ") in setSelectedText, ignoring.");
135 }
136 }
137 }
138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 public void setCursorCapsMode(int capsMode, int seq) {
140 synchronized (this) {
141 if (seq == mSeq) {
142 mCursorCapsMode = capsMode;
143 mHaveValue = true;
144 notifyAll();
145 } else {
146 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
147 + ") in setCursorCapsMode, ignoring.");
148 }
149 }
150 }
151
152 public void setExtractedText(ExtractedText extractedText, int seq) {
153 synchronized (this) {
154 if (seq == mSeq) {
155 mExtractedText = extractedText;
156 mHaveValue = true;
157 notifyAll();
158 } else {
159 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
160 + ") in setExtractedText, ignoring.");
161 }
162 }
163 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900164
Yohei Yukawaa277db22014-08-21 18:38:44 -0700165 public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900166 synchronized (this) {
167 if (seq == mSeq) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700168 mRequestUpdateCursorAnchorInfoResult = result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900169 mHaveValue = true;
170 notifyAll();
171 } else {
172 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
173 + ") in setCursorAnchorInfoRequestResult, ignoring.");
174 }
175 }
176 }
177
Yohei Yukawa152944f2016-06-10 19:04:34 -0700178 public void setInsertContentResult(boolean result, int seq) {
179 synchronized (this) {
180 if (seq == mSeq) {
181 mInsertContentResult = result;
182 mHaveValue = true;
183 notifyAll();
184 } else {
185 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
186 + ") in setInsertContentResult, ignoring.");
187 }
188 }
189 }
190
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 /**
192 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
193 *
194 * <p>The caller must be synchronized on this callback object.
195 */
196 void waitForResultLocked() {
197 long startTime = SystemClock.uptimeMillis();
198 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
199
200 while (!mHaveValue) {
201 long remainingTime = endTime - SystemClock.uptimeMillis();
202 if (remainingTime <= 0) {
203 Log.w(TAG, "Timed out waiting on IInputContextCallback");
204 return;
205 }
206 try {
207 wait(remainingTime);
208 } catch (InterruptedException e) {
209 }
210 }
211 }
212 }
213
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700214 public InputConnectionWrapper(IInputContext inputContext,
215 @MissingMethodFlags final int missingMethods) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 mIInputContext = inputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700217 mMissingMethods = missingMethods;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 }
219
220 public CharSequence getTextAfterCursor(int length, int flags) {
221 CharSequence value = null;
222 try {
223 InputContextCallback callback = InputContextCallback.getInstance();
224 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
225 synchronized (callback) {
226 callback.waitForResultLocked();
227 if (callback.mHaveValue) {
228 value = callback.mTextAfterCursor;
229 }
230 }
231 callback.dispose();
232 } catch (RemoteException e) {
233 return null;
234 }
235 return value;
236 }
237
238 public CharSequence getTextBeforeCursor(int length, int flags) {
239 CharSequence value = null;
240 try {
241 InputContextCallback callback = InputContextCallback.getInstance();
242 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
243 synchronized (callback) {
244 callback.waitForResultLocked();
245 if (callback.mHaveValue) {
246 value = callback.mTextBeforeCursor;
247 }
248 }
249 callback.dispose();
250 } catch (RemoteException e) {
251 return null;
252 }
253 return value;
254 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700255
Amith Yamasania90b7f02010-08-25 18:27:20 -0700256 public CharSequence getSelectedText(int flags) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700257 if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
258 // This method is not implemented.
259 return null;
260 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700261 CharSequence value = null;
262 try {
263 InputContextCallback callback = InputContextCallback.getInstance();
264 mIInputContext.getSelectedText(flags, callback.mSeq, callback);
265 synchronized (callback) {
266 callback.waitForResultLocked();
267 if (callback.mHaveValue) {
268 value = callback.mSelectedText;
269 }
270 }
271 callback.dispose();
272 } catch (RemoteException e) {
273 return null;
274 }
275 return value;
276 }
277
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 public int getCursorCapsMode(int reqModes) {
279 int value = 0;
280 try {
281 InputContextCallback callback = InputContextCallback.getInstance();
282 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
283 synchronized (callback) {
284 callback.waitForResultLocked();
285 if (callback.mHaveValue) {
286 value = callback.mCursorCapsMode;
287 }
288 }
289 callback.dispose();
290 } catch (RemoteException e) {
291 return 0;
292 }
293 return value;
294 }
satoke3797a12011-03-22 06:34:48 +0900295
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
297 ExtractedText value = null;
298 try {
299 InputContextCallback callback = InputContextCallback.getInstance();
300 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
301 synchronized (callback) {
302 callback.waitForResultLocked();
303 if (callback.mHaveValue) {
304 value = callback.mExtractedText;
305 }
306 }
307 callback.dispose();
308 } catch (RemoteException e) {
309 return null;
310 }
311 return value;
312 }
313
314 public boolean commitText(CharSequence text, int newCursorPosition) {
315 try {
316 mIInputContext.commitText(text, newCursorPosition);
317 return true;
318 } catch (RemoteException e) {
319 return false;
320 }
321 }
322
323 public boolean commitCompletion(CompletionInfo text) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700324 if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
325 // This method is not implemented.
326 return false;
327 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 try {
329 mIInputContext.commitCompletion(text);
330 return true;
331 } catch (RemoteException e) {
332 return false;
333 }
334 }
335
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800336 public boolean commitCorrection(CorrectionInfo correctionInfo) {
337 try {
338 mIInputContext.commitCorrection(correctionInfo);
339 return true;
340 } catch (RemoteException e) {
341 return false;
342 }
343 }
344
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 public boolean setSelection(int start, int end) {
346 try {
347 mIInputContext.setSelection(start, end);
348 return true;
349 } catch (RemoteException e) {
350 return false;
351 }
352 }
353
354 public boolean performEditorAction(int actionCode) {
355 try {
356 mIInputContext.performEditorAction(actionCode);
357 return true;
358 } catch (RemoteException e) {
359 return false;
360 }
361 }
362
363 public boolean performContextMenuAction(int id) {
364 try {
365 mIInputContext.performContextMenuAction(id);
366 return true;
367 } catch (RemoteException e) {
368 return false;
369 }
370 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700371
372 public boolean setComposingRegion(int start, int end) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700373 if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
374 // This method is not implemented.
375 return false;
376 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700377 try {
378 mIInputContext.setComposingRegion(start, end);
379 return true;
380 } catch (RemoteException e) {
381 return false;
382 }
383 }
384
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 public boolean setComposingText(CharSequence text, int newCursorPosition) {
386 try {
387 mIInputContext.setComposingText(text, newCursorPosition);
388 return true;
389 } catch (RemoteException e) {
390 return false;
391 }
392 }
393
394 public boolean finishComposingText() {
395 try {
396 mIInputContext.finishComposingText();
397 return true;
398 } catch (RemoteException e) {
399 return false;
400 }
401 }
402
403 public boolean beginBatchEdit() {
404 try {
405 mIInputContext.beginBatchEdit();
406 return true;
407 } catch (RemoteException e) {
408 return false;
409 }
410 }
411
412 public boolean endBatchEdit() {
413 try {
414 mIInputContext.endBatchEdit();
415 return true;
416 } catch (RemoteException e) {
417 return false;
418 }
419 }
420
421 public boolean sendKeyEvent(KeyEvent event) {
422 try {
423 mIInputContext.sendKeyEvent(event);
424 return true;
425 } catch (RemoteException e) {
426 return false;
427 }
428 }
429
430 public boolean clearMetaKeyStates(int states) {
431 try {
432 mIInputContext.clearMetaKeyStates(states);
433 return true;
434 } catch (RemoteException e) {
435 return false;
436 }
437 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800438
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800439 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 try {
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800441 mIInputContext.deleteSurroundingText(beforeLength, afterLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 return true;
443 } catch (RemoteException e) {
444 return false;
445 }
446 }
447
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800448 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700449 if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
450 // This method is not implemented.
451 return false;
452 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800453 try {
454 mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
455 return true;
456 } catch (RemoteException e) {
457 return false;
458 }
459 }
460
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 public boolean reportFullscreenMode(boolean enabled) {
462 try {
463 mIInputContext.reportFullscreenMode(enabled);
464 return true;
465 } catch (RemoteException e) {
466 return false;
467 }
468 }
469
470 public boolean performPrivateCommand(String action, Bundle data) {
471 try {
472 mIInputContext.performPrivateCommand(action, data);
473 return true;
474 } catch (RemoteException e) {
475 return false;
476 }
477 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900478
Yohei Yukawad8636ea2014-09-02 22:03:30 -0700479 public boolean requestCursorUpdates(int cursorUpdateMode) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700480 boolean result = false;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700481 if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
482 // This method is not implemented.
483 return false;
484 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900485 try {
486 InputContextCallback callback = InputContextCallback.getInstance();
Yohei Yukawaa277db22014-08-21 18:38:44 -0700487 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900488 synchronized (callback) {
489 callback.waitForResultLocked();
490 if (callback.mHaveValue) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700491 result = callback.mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900492 }
493 }
494 callback.dispose();
495 } catch (RemoteException e) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700496 return false;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900497 }
Yohei Yukawaa277db22014-08-21 18:38:44 -0700498 return result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900499 }
Yohei Yukawa612cce92016-02-11 17:47:33 -0800500
501 public Handler getHandler() {
502 // Nothing should happen when called from input method.
503 return null;
504 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700505
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700506 public void closeConnection() {
507 // Nothing should happen when called from input method.
508 }
509
Yohei Yukawa152944f2016-06-10 19:04:34 -0700510 public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
511 boolean result = false;
512 if (isMethodMissing(MissingMethodFlags.INSERT_CONTENT)) {
513 // This method is not implemented.
514 return false;
515 }
516 try {
517 InputContextCallback callback = InputContextCallback.getInstance();
518 mIInputContext.insertContent(inputContentInfo, opts, callback.mSeq, callback);
519 synchronized (callback) {
520 callback.waitForResultLocked();
521 if (callback.mHaveValue) {
522 result = callback.mInsertContentResult;
523 }
524 }
525 callback.dispose();
526 } catch (RemoteException e) {
527 return false;
528 }
529 return result;
530 }
531
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700532 private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
533 return (mMissingMethods & methodFlag) == methodFlag;
534 }
535
536 @Override
537 public String toString() {
538 return "InputConnectionWrapper{idHash=#"
539 + Integer.toHexString(System.identityHashCode(this))
540 + " mMissingMethods="
541 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";
542 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543}