blob: cc0ef756c6b3695028139def6e5c27c79a6be643 [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 Yukawa45700fa2016-06-23 17:12:59 -070019import android.annotation.NonNull;
20import android.inputmethodservice.AbstractInputMethodService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.os.Bundle;
Yohei Yukawa612cce92016-02-11 17:47:33 -080022import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.os.RemoteException;
24import android.os.SystemClock;
25import android.util.Log;
26import android.view.KeyEvent;
27import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -080028import android.view.inputmethod.CorrectionInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.inputmethod.ExtractedText;
30import android.view.inputmethod.ExtractedTextRequest;
31import android.view.inputmethod.InputConnection;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070032import android.view.inputmethod.InputConnectionInspector;
33import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
Yohei Yukawa152944f2016-06-10 19:04:34 -070034import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
Yohei Yukawa45700fa2016-06-23 17:12:59 -070036import java.lang.ref.WeakReference;
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038public class InputConnectionWrapper implements InputConnection {
39 private static final int MAX_WAIT_TIME_MILLIS = 2000;
40 private final IInputContext mIInputContext;
Yohei Yukawa45700fa2016-06-23 17:12:59 -070041 @NonNull
42 private final WeakReference<AbstractInputMethodService> mInputMethodService;
43
Yohei Yukawa19a80a12016-03-14 22:57:37 -070044 @MissingMethodFlags
45 private final int mMissingMethods;
46
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 static class InputContextCallback extends IInputContextCallback.Stub {
48 private static final String TAG = "InputConnectionWrapper.ICC";
49 public int mSeq;
50 public boolean mHaveValue;
51 public CharSequence mTextBeforeCursor;
52 public CharSequence mTextAfterCursor;
Amith Yamasania90b7f02010-08-25 18:27:20 -070053 public CharSequence mSelectedText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 public ExtractedText mExtractedText;
55 public int mCursorCapsMode;
Yohei Yukawaa277db22014-08-21 18:38:44 -070056 public boolean mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawaadebb522016-06-17 10:10:39 -070057 public boolean mCommitContentResult;
Yohei Yukawa152944f2016-06-10 19:04:34 -070058
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
60 // exclusive access to this object.
61 private static InputContextCallback sInstance = new InputContextCallback();
62 private static int sSequenceNumber = 1;
63
64 /**
65 * Returns an InputContextCallback object that is guaranteed not to be in use by
66 * any other thread. The returned object's 'have value' flag is cleared and its expected
67 * sequence number is set to a new integer. We use a sequence number so that replies that
68 * occur after a timeout has expired are not interpreted as replies to a later request.
69 */
70 private static InputContextCallback getInstance() {
71 synchronized (InputContextCallback.class) {
72 // Return sInstance if it's non-null, otherwise construct a new callback
73 InputContextCallback callback;
74 if (sInstance != null) {
75 callback = sInstance;
76 sInstance = null;
77
78 // Reset the callback
79 callback.mHaveValue = false;
80 } else {
81 callback = new InputContextCallback();
82 }
83
84 // Set the sequence number
85 callback.mSeq = sSequenceNumber++;
86 return callback;
87 }
88 }
89
90 /**
91 * Makes the given InputContextCallback available for use in the future.
92 */
93 private void dispose() {
94 synchronized (InputContextCallback.class) {
95 // If sInstance is non-null, just let this object be garbage-collected
96 if (sInstance == null) {
97 // Allow any objects being held to be gc'ed
98 mTextAfterCursor = null;
99 mTextBeforeCursor = null;
100 mExtractedText = null;
101 sInstance = this;
102 }
103 }
104 }
105
106 public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
107 synchronized (this) {
108 if (seq == mSeq) {
109 mTextBeforeCursor = textBeforeCursor;
110 mHaveValue = true;
111 notifyAll();
112 } else {
113 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
114 + ") in setTextBeforeCursor, ignoring.");
115 }
116 }
117 }
118
119 public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
120 synchronized (this) {
121 if (seq == mSeq) {
122 mTextAfterCursor = textAfterCursor;
123 mHaveValue = true;
124 notifyAll();
125 } else {
126 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
127 + ") in setTextAfterCursor, ignoring.");
128 }
129 }
130 }
131
Amith Yamasania90b7f02010-08-25 18:27:20 -0700132 public void setSelectedText(CharSequence selectedText, int seq) {
133 synchronized (this) {
134 if (seq == mSeq) {
135 mSelectedText = selectedText;
136 mHaveValue = true;
137 notifyAll();
138 } else {
139 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
140 + ") in setSelectedText, ignoring.");
141 }
142 }
143 }
144
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 public void setCursorCapsMode(int capsMode, int seq) {
146 synchronized (this) {
147 if (seq == mSeq) {
148 mCursorCapsMode = capsMode;
149 mHaveValue = true;
150 notifyAll();
151 } else {
152 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
153 + ") in setCursorCapsMode, ignoring.");
154 }
155 }
156 }
157
158 public void setExtractedText(ExtractedText extractedText, int seq) {
159 synchronized (this) {
160 if (seq == mSeq) {
161 mExtractedText = extractedText;
162 mHaveValue = true;
163 notifyAll();
164 } else {
165 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
166 + ") in setExtractedText, ignoring.");
167 }
168 }
169 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900170
Yohei Yukawaa277db22014-08-21 18:38:44 -0700171 public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900172 synchronized (this) {
173 if (seq == mSeq) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700174 mRequestUpdateCursorAnchorInfoResult = result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900175 mHaveValue = true;
176 notifyAll();
177 } else {
178 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
179 + ") in setCursorAnchorInfoRequestResult, ignoring.");
180 }
181 }
182 }
183
Yohei Yukawaadebb522016-06-17 10:10:39 -0700184 public void setCommitContentResult(boolean result, int seq) {
Yohei Yukawa152944f2016-06-10 19:04:34 -0700185 synchronized (this) {
186 if (seq == mSeq) {
Yohei Yukawaadebb522016-06-17 10:10:39 -0700187 mCommitContentResult = result;
Yohei Yukawa152944f2016-06-10 19:04:34 -0700188 mHaveValue = true;
189 notifyAll();
190 } else {
191 Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
Yohei Yukawaadebb522016-06-17 10:10:39 -0700192 + ") in setCommitContentResult, ignoring.");
Yohei Yukawa152944f2016-06-10 19:04:34 -0700193 }
194 }
195 }
196
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 /**
198 * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
199 *
200 * <p>The caller must be synchronized on this callback object.
201 */
202 void waitForResultLocked() {
203 long startTime = SystemClock.uptimeMillis();
204 long endTime = startTime + MAX_WAIT_TIME_MILLIS;
205
206 while (!mHaveValue) {
207 long remainingTime = endTime - SystemClock.uptimeMillis();
208 if (remainingTime <= 0) {
209 Log.w(TAG, "Timed out waiting on IInputContextCallback");
210 return;
211 }
212 try {
213 wait(remainingTime);
214 } catch (InterruptedException e) {
215 }
216 }
217 }
218 }
219
Yohei Yukawa45700fa2016-06-23 17:12:59 -0700220 public InputConnectionWrapper(
221 @NonNull WeakReference<AbstractInputMethodService> inputMethodService,
222 IInputContext inputContext, @MissingMethodFlags final int missingMethods) {
223 mInputMethodService = inputMethodService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 mIInputContext = inputContext;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700225 mMissingMethods = missingMethods;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 }
227
228 public CharSequence getTextAfterCursor(int length, int flags) {
229 CharSequence value = null;
230 try {
231 InputContextCallback callback = InputContextCallback.getInstance();
232 mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
233 synchronized (callback) {
234 callback.waitForResultLocked();
235 if (callback.mHaveValue) {
236 value = callback.mTextAfterCursor;
237 }
238 }
239 callback.dispose();
240 } catch (RemoteException e) {
241 return null;
242 }
243 return value;
244 }
245
246 public CharSequence getTextBeforeCursor(int length, int flags) {
247 CharSequence value = null;
248 try {
249 InputContextCallback callback = InputContextCallback.getInstance();
250 mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
251 synchronized (callback) {
252 callback.waitForResultLocked();
253 if (callback.mHaveValue) {
254 value = callback.mTextBeforeCursor;
255 }
256 }
257 callback.dispose();
258 } catch (RemoteException e) {
259 return null;
260 }
261 return value;
262 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700263
Amith Yamasania90b7f02010-08-25 18:27:20 -0700264 public CharSequence getSelectedText(int flags) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700265 if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
266 // This method is not implemented.
267 return null;
268 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700269 CharSequence value = null;
270 try {
271 InputContextCallback callback = InputContextCallback.getInstance();
272 mIInputContext.getSelectedText(flags, callback.mSeq, callback);
273 synchronized (callback) {
274 callback.waitForResultLocked();
275 if (callback.mHaveValue) {
276 value = callback.mSelectedText;
277 }
278 }
279 callback.dispose();
280 } catch (RemoteException e) {
281 return null;
282 }
283 return value;
284 }
285
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 public int getCursorCapsMode(int reqModes) {
287 int value = 0;
288 try {
289 InputContextCallback callback = InputContextCallback.getInstance();
290 mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
291 synchronized (callback) {
292 callback.waitForResultLocked();
293 if (callback.mHaveValue) {
294 value = callback.mCursorCapsMode;
295 }
296 }
297 callback.dispose();
298 } catch (RemoteException e) {
299 return 0;
300 }
301 return value;
302 }
satoke3797a12011-03-22 06:34:48 +0900303
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
305 ExtractedText value = null;
306 try {
307 InputContextCallback callback = InputContextCallback.getInstance();
308 mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
309 synchronized (callback) {
310 callback.waitForResultLocked();
311 if (callback.mHaveValue) {
312 value = callback.mExtractedText;
313 }
314 }
315 callback.dispose();
316 } catch (RemoteException e) {
317 return null;
318 }
319 return value;
320 }
321
322 public boolean commitText(CharSequence text, int newCursorPosition) {
323 try {
324 mIInputContext.commitText(text, newCursorPosition);
325 return true;
326 } catch (RemoteException e) {
327 return false;
328 }
329 }
330
331 public boolean commitCompletion(CompletionInfo text) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700332 if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
333 // This method is not implemented.
334 return false;
335 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 try {
337 mIInputContext.commitCompletion(text);
338 return true;
339 } catch (RemoteException e) {
340 return false;
341 }
342 }
343
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800344 public boolean commitCorrection(CorrectionInfo correctionInfo) {
345 try {
346 mIInputContext.commitCorrection(correctionInfo);
347 return true;
348 } catch (RemoteException e) {
349 return false;
350 }
351 }
352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 public boolean setSelection(int start, int end) {
354 try {
355 mIInputContext.setSelection(start, end);
356 return true;
357 } catch (RemoteException e) {
358 return false;
359 }
360 }
361
362 public boolean performEditorAction(int actionCode) {
363 try {
364 mIInputContext.performEditorAction(actionCode);
365 return true;
366 } catch (RemoteException e) {
367 return false;
368 }
369 }
370
371 public boolean performContextMenuAction(int id) {
372 try {
373 mIInputContext.performContextMenuAction(id);
374 return true;
375 } catch (RemoteException e) {
376 return false;
377 }
378 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700379
380 public boolean setComposingRegion(int start, int end) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700381 if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
382 // This method is not implemented.
383 return false;
384 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700385 try {
386 mIInputContext.setComposingRegion(start, end);
387 return true;
388 } catch (RemoteException e) {
389 return false;
390 }
391 }
392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 public boolean setComposingText(CharSequence text, int newCursorPosition) {
394 try {
395 mIInputContext.setComposingText(text, newCursorPosition);
396 return true;
397 } catch (RemoteException e) {
398 return false;
399 }
400 }
401
402 public boolean finishComposingText() {
403 try {
404 mIInputContext.finishComposingText();
405 return true;
406 } catch (RemoteException e) {
407 return false;
408 }
409 }
410
411 public boolean beginBatchEdit() {
412 try {
413 mIInputContext.beginBatchEdit();
414 return true;
415 } catch (RemoteException e) {
416 return false;
417 }
418 }
419
420 public boolean endBatchEdit() {
421 try {
422 mIInputContext.endBatchEdit();
423 return true;
424 } catch (RemoteException e) {
425 return false;
426 }
427 }
428
429 public boolean sendKeyEvent(KeyEvent event) {
430 try {
431 mIInputContext.sendKeyEvent(event);
432 return true;
433 } catch (RemoteException e) {
434 return false;
435 }
436 }
437
438 public boolean clearMetaKeyStates(int states) {
439 try {
440 mIInputContext.clearMetaKeyStates(states);
441 return true;
442 } catch (RemoteException e) {
443 return false;
444 }
445 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800446
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800447 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 try {
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800449 mIInputContext.deleteSurroundingText(beforeLength, afterLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 return true;
451 } catch (RemoteException e) {
452 return false;
453 }
454 }
455
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800456 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700457 if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
458 // This method is not implemented.
459 return false;
460 }
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800461 try {
462 mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
463 return true;
464 } catch (RemoteException e) {
465 return false;
466 }
467 }
468
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 public boolean reportFullscreenMode(boolean enabled) {
Yohei Yukawa2bc66172017-02-08 11:13:25 -0800470 // Nothing should happen when called from input method.
471 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 }
473
474 public boolean performPrivateCommand(String action, Bundle data) {
475 try {
476 mIInputContext.performPrivateCommand(action, data);
477 return true;
478 } catch (RemoteException e) {
479 return false;
480 }
481 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900482
Yohei Yukawad8636ea2014-09-02 22:03:30 -0700483 public boolean requestCursorUpdates(int cursorUpdateMode) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700484 boolean result = false;
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700485 if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
486 // This method is not implemented.
487 return false;
488 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900489 try {
490 InputContextCallback callback = InputContextCallback.getInstance();
Yohei Yukawaa277db22014-08-21 18:38:44 -0700491 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900492 synchronized (callback) {
493 callback.waitForResultLocked();
494 if (callback.mHaveValue) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700495 result = callback.mRequestUpdateCursorAnchorInfoResult;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900496 }
497 }
498 callback.dispose();
499 } catch (RemoteException e) {
Yohei Yukawaa277db22014-08-21 18:38:44 -0700500 return false;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900501 }
Yohei Yukawaa277db22014-08-21 18:38:44 -0700502 return result;
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900503 }
Yohei Yukawa612cce92016-02-11 17:47:33 -0800504
505 public Handler getHandler() {
506 // Nothing should happen when called from input method.
507 return null;
508 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700509
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700510 public void closeConnection() {
511 // Nothing should happen when called from input method.
512 }
513
Yohei Yukawa45700fa2016-06-23 17:12:59 -0700514 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
Yohei Yukawa152944f2016-06-10 19:04:34 -0700515 boolean result = false;
Yohei Yukawaadebb522016-06-17 10:10:39 -0700516 if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
Yohei Yukawa152944f2016-06-10 19:04:34 -0700517 // This method is not implemented.
518 return false;
519 }
520 try {
Yohei Yukawa79d1c752016-06-30 19:24:04 +0000521 if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
Yohei Yukawa45700fa2016-06-23 17:12:59 -0700522 final AbstractInputMethodService inputMethodService = mInputMethodService.get();
523 if (inputMethodService == null) {
524 // This basically should not happen, because it's the the caller of this method.
525 return false;
526 }
527 inputMethodService.exposeContent(inputContentInfo, this);
528 }
Yohei Yukawa79d1c752016-06-30 19:24:04 +0000529
Yohei Yukawa152944f2016-06-10 19:04:34 -0700530 InputContextCallback callback = InputContextCallback.getInstance();
Yohei Yukawa45700fa2016-06-23 17:12:59 -0700531 mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
Yohei Yukawa152944f2016-06-10 19:04:34 -0700532 synchronized (callback) {
533 callback.waitForResultLocked();
534 if (callback.mHaveValue) {
Yohei Yukawaadebb522016-06-17 10:10:39 -0700535 result = callback.mCommitContentResult;
Yohei Yukawa152944f2016-06-10 19:04:34 -0700536 }
537 }
538 callback.dispose();
539 } catch (RemoteException e) {
540 return false;
541 }
542 return result;
543 }
544
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700545 private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
546 return (mMissingMethods & methodFlag) == methodFlag;
547 }
548
549 @Override
550 public String toString() {
551 return "InputConnectionWrapper{idHash=#"
552 + Integer.toHexString(System.identityHashCode(this))
553 + " mMissingMethods="
554 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";
555 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556}