1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | package org.chromium.content.browser.input; |
6 | |
7 | import android.os.Handler; |
8 | import android.os.ResultReceiver; |
9 | import android.view.KeyCharacterMap; |
10 | import android.view.KeyEvent; |
11 | import android.view.View; |
12 | import android.view.inputmethod.EditorInfo; |
13 | |
14 | import com.google.common.annotations.VisibleForTesting; |
15 | |
16 | import org.chromium.base.CalledByNative; |
17 | import org.chromium.base.JNINamespace; |
18 | |
19 | /** |
20 | * Adapts and plumbs android IME service onto the chrome text input API. |
21 | * ImeAdapter provides an interface in both ways native <-> java: |
22 | * 1. InputConnectionAdapter notifies native code of text composition state and |
23 | * dispatch key events from java -> WebKit. |
24 | * 2. Native ImeAdapter notifies java side to clear composition text. |
25 | * |
26 | * The basic flow is: |
27 | * 1. When InputConnectionAdapter gets called with composition or result text: |
28 | * If we receive a composition text or a result text, then we just need to |
29 | * dispatch a synthetic key event with special keycode 229, and then dispatch |
30 | * the composition or result text. |
31 | * 2. Intercept dispatchKeyEvent() method for key events not handled by IME, we |
32 | * need to dispatch them to webkit and check webkit's reply. Then inject a |
33 | * new key event for further processing if webkit didn't handle it. |
34 | * |
35 | * Note that the native peer object does not take any strong reference onto the |
36 | * instance of this java object, hence it is up to the client of this class (e.g. |
37 | * the ViewEmbedder implementor) to hold a strong reference to it for the required |
38 | * lifetime of the object. |
39 | */ |
40 | @JNINamespace("content") |
41 | public class ImeAdapter { |
42 | public interface ImeAdapterDelegate { |
43 | /** |
44 | * @param isFinish whether the event is occurring because input is finished. |
45 | */ |
46 | void onImeEvent(boolean isFinish); |
47 | void onSetFieldValue(); |
48 | void onDismissInput(); |
49 | View getAttachedView(); |
50 | ResultReceiver getNewShowKeyboardReceiver(); |
51 | } |
52 | |
53 | private class DelayedDismissInput implements Runnable { |
54 | private final int mNativeImeAdapter; |
55 | |
56 | DelayedDismissInput(int nativeImeAdapter) { |
57 | mNativeImeAdapter = nativeImeAdapter; |
58 | } |
59 | |
60 | @Override |
61 | public void run() { |
62 | attach(mNativeImeAdapter, sTextInputTypeNone, AdapterInputConnection.INVALID_SELECTION, |
63 | AdapterInputConnection.INVALID_SELECTION); |
64 | dismissInput(true); |
65 | } |
66 | } |
67 | |
68 | private static final int COMPOSITION_KEY_CODE = 229; |
69 | |
70 | // Delay introduced to avoid hiding the keyboard if new show requests are received. |
71 | // The time required by the unfocus-focus events triggered by tab has been measured in soju: |
72 | // Mean: 18.633 ms, Standard deviation: 7.9837 ms. |
73 | // The value here should be higher enough to cover these cases, but not too high to avoid |
74 | // letting the user perceiving important delays. |
75 | private static final int INPUT_DISMISS_DELAY = 150; |
76 | |
77 | // All the constants that are retrieved from the C++ code. |
78 | // They get set through initializeWebInputEvents and initializeTextInputTypes calls. |
79 | static int sEventTypeRawKeyDown; |
80 | static int sEventTypeKeyUp; |
81 | static int sEventTypeChar; |
82 | static int sTextInputTypeNone; |
83 | static int sTextInputTypeText; |
84 | static int sTextInputTypeTextArea; |
85 | static int sTextInputTypePassword; |
86 | static int sTextInputTypeSearch; |
87 | static int sTextInputTypeUrl; |
88 | static int sTextInputTypeEmail; |
89 | static int sTextInputTypeTel; |
90 | static int sTextInputTypeNumber; |
91 | static int sTextInputTypeWeek; |
92 | static int sTextInputTypeContentEditable; |
93 | static int sModifierShift; |
94 | static int sModifierAlt; |
95 | static int sModifierCtrl; |
96 | static int sModifierCapsLockOn; |
97 | static int sModifierNumLockOn; |
98 | |
99 | private int mNativeImeAdapterAndroid; |
100 | private InputMethodManagerWrapper mInputMethodManagerWrapper; |
101 | private AdapterInputConnection mInputConnection; |
102 | private final ImeAdapterDelegate mViewEmbedder; |
103 | private final Handler mHandler; |
104 | private DelayedDismissInput mDismissInput = null; |
105 | private int mTextInputType; |
106 | private int mInitialSelectionStart; |
107 | private int mInitialSelectionEnd; |
108 | |
109 | @VisibleForTesting |
110 | boolean mIsShowWithoutHideOutstanding = false; |
111 | |
112 | /** |
113 | * @param wrapper InputMethodManagerWrapper that should receive all the call directed to |
114 | * InputMethodManager. |
115 | * @param embedder The view that is used for callbacks from ImeAdapter. |
116 | */ |
117 | public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) { |
118 | mInputMethodManagerWrapper = wrapper; |
119 | mViewEmbedder = embedder; |
120 | mHandler = new Handler(); |
121 | } |
122 | |
123 | public static class AdapterInputConnectionFactory { |
124 | public AdapterInputConnection get(View view, ImeAdapter imeAdapter, |
125 | EditorInfo outAttrs) { |
126 | return new AdapterInputConnection(view, imeAdapter, outAttrs); |
127 | } |
128 | } |
129 | |
130 | @VisibleForTesting |
131 | public void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) { |
132 | mInputMethodManagerWrapper = immw; |
133 | } |
134 | |
135 | /** |
136 | * Should be only used by AdapterInputConnection. |
137 | * @return InputMethodManagerWrapper that should receive all the calls directed to |
138 | * InputMethodManager. |
139 | */ |
140 | InputMethodManagerWrapper getInputMethodManagerWrapper() { |
141 | return mInputMethodManagerWrapper; |
142 | } |
143 | |
144 | /** |
145 | * Set the current active InputConnection when a new InputConnection is constructed. |
146 | * @param inputConnection The input connection that is currently used with IME. |
147 | */ |
148 | void setInputConnection(AdapterInputConnection inputConnection) { |
149 | mInputConnection = inputConnection; |
150 | } |
151 | |
152 | /** |
153 | * Should be only used by AdapterInputConnection. |
154 | * @return The input type of currently focused element. |
155 | */ |
156 | int getTextInputType() { |
157 | return mTextInputType; |
158 | } |
159 | |
160 | /** |
161 | * Should be only used by AdapterInputConnection. |
162 | * @return The starting index of the initial text selection. |
163 | */ |
164 | int getInitialSelectionStart() { |
165 | return mInitialSelectionStart; |
166 | } |
167 | |
168 | /** |
169 | * Should be only used by AdapterInputConnection. |
170 | * @return The ending index of the initial text selection. |
171 | */ |
172 | int getInitialSelectionEnd() { |
173 | return mInitialSelectionEnd; |
174 | } |
175 | |
176 | public static int getTextInputTypeNone() { |
177 | return sTextInputTypeNone; |
178 | } |
179 | |
180 | private static int getModifiers(int metaState) { |
181 | int modifiers = 0; |
182 | if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { |
183 | modifiers |= sModifierShift; |
184 | } |
185 | if ((metaState & KeyEvent.META_ALT_ON) != 0) { |
186 | modifiers |= sModifierAlt; |
187 | } |
188 | if ((metaState & KeyEvent.META_CTRL_ON) != 0) { |
189 | modifiers |= sModifierCtrl; |
190 | } |
191 | if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) { |
192 | modifiers |= sModifierCapsLockOn; |
193 | } |
194 | if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) { |
195 | modifiers |= sModifierNumLockOn; |
196 | } |
197 | return modifiers; |
198 | } |
199 | |
200 | public boolean isActive() { |
201 | return mInputConnection != null && mInputConnection.isActive(); |
202 | } |
203 | |
204 | private boolean isFor(int nativeImeAdapter, int textInputType) { |
205 | return mNativeImeAdapterAndroid == nativeImeAdapter && |
206 | mTextInputType == textInputType; |
207 | } |
208 | |
209 | public void attachAndShowIfNeeded(int nativeImeAdapter, int textInputType, |
210 | int selectionStart, int selectionEnd, boolean showIfNeeded) { |
211 | mHandler.removeCallbacks(mDismissInput); |
212 | |
213 | // If current input type is none and showIfNeeded is false, IME should not be shown |
214 | // and input type should remain as none. |
215 | if (mTextInputType == sTextInputTypeNone && !showIfNeeded) { |
216 | return; |
217 | } |
218 | |
219 | if (!isFor(nativeImeAdapter, textInputType)) { |
220 | // Set a delayed task to perform unfocus. This avoids hiding the keyboard when tabbing |
221 | // through text inputs or when JS rapidly changes focus to another text element. |
222 | if (textInputType == sTextInputTypeNone) { |
223 | mDismissInput = new DelayedDismissInput(nativeImeAdapter); |
224 | mHandler.postDelayed(mDismissInput, INPUT_DISMISS_DELAY); |
225 | return; |
226 | } |
227 | |
228 | int previousType = mTextInputType; |
229 | attach(nativeImeAdapter, textInputType, selectionStart, selectionEnd); |
230 | |
231 | mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView()); |
232 | if (showIfNeeded) { |
233 | showKeyboard(); |
234 | } |
235 | } else if (hasInputType() && showIfNeeded) { |
236 | showKeyboard(); |
237 | } |
238 | } |
239 | |
240 | public void attach(int nativeImeAdapter, int textInputType, int selectionStart, |
241 | int selectionEnd) { |
242 | if (mNativeImeAdapterAndroid != 0) { |
243 | nativeResetImeAdapter(mNativeImeAdapterAndroid); |
244 | } |
245 | mNativeImeAdapterAndroid = nativeImeAdapter; |
246 | mTextInputType = textInputType; |
247 | mInitialSelectionStart = selectionStart; |
248 | mInitialSelectionEnd = selectionEnd; |
249 | if (nativeImeAdapter != 0) { |
250 | nativeAttachImeAdapter(mNativeImeAdapterAndroid); |
251 | } |
252 | } |
253 | |
254 | /** |
255 | * Attaches the imeAdapter to its native counterpart. This is needed to start forwarding |
256 | * keyboard events to WebKit. |
257 | * @param nativeImeAdapter The pointer to the native ImeAdapter object. |
258 | */ |
259 | public void attach(int nativeImeAdapter) { |
260 | if (mNativeImeAdapterAndroid != 0) { |
261 | nativeResetImeAdapter(mNativeImeAdapterAndroid); |
262 | } |
263 | mNativeImeAdapterAndroid = nativeImeAdapter; |
264 | if (nativeImeAdapter != 0) { |
265 | nativeAttachImeAdapter(mNativeImeAdapterAndroid); |
266 | } |
267 | } |
268 | |
269 | /** |
270 | * Used to check whether the native counterpart of the ImeAdapter has been attached yet. |
271 | * @return Whether native ImeAdapter has been attached and its pointer is currently nonzero. |
272 | */ |
273 | public boolean isNativeImeAdapterAttached() { |
274 | return mNativeImeAdapterAndroid != 0; |
275 | } |
276 | |
277 | private void showKeyboard() { |
278 | mIsShowWithoutHideOutstanding = true; |
279 | mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0, |
280 | mViewEmbedder.getNewShowKeyboardReceiver()); |
281 | } |
282 | |
283 | private void dismissInput(boolean unzoomIfNeeded) { |
284 | hideKeyboard(unzoomIfNeeded); |
285 | mViewEmbedder.onDismissInput(); |
286 | } |
287 | |
288 | private void hideKeyboard(boolean unzoomIfNeeded) { |
289 | mIsShowWithoutHideOutstanding = false; |
290 | View view = mViewEmbedder.getAttachedView(); |
291 | if (mInputMethodManagerWrapper.isActive(view)) { |
292 | mInputMethodManagerWrapper.hideSoftInputFromWindow(view.getWindowToken(), 0, |
293 | unzoomIfNeeded ? mViewEmbedder.getNewShowKeyboardReceiver() : null); |
294 | } |
295 | } |
296 | |
297 | private boolean hasInputType() { |
298 | return mTextInputType != sTextInputTypeNone; |
299 | } |
300 | |
301 | static boolean isTextInputType(int type) { |
302 | return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type); |
303 | } |
304 | |
305 | public boolean hasTextInputType() { |
306 | return isTextInputType(mTextInputType); |
307 | } |
308 | |
309 | public boolean dispatchKeyEvent(KeyEvent event) { |
310 | return translateAndSendNativeEvents(event); |
311 | } |
312 | |
313 | private int shouldSendKeyEventWithKeyCode(String text) { |
314 | if (text.length() != 1) return COMPOSITION_KEY_CODE; |
315 | |
316 | if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; |
317 | else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; |
318 | else return COMPOSITION_KEY_CODE; |
319 | } |
320 | |
321 | void sendKeyEventWithKeyCode(int keyCode, int flags) { |
322 | long eventTime = System.currentTimeMillis(); |
323 | translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, |
324 | KeyEvent.ACTION_DOWN, keyCode, 0, 0, |
325 | KeyCharacterMap.VIRTUAL_KEYBOARD, 0, |
326 | flags)); |
327 | translateAndSendNativeEvents(new KeyEvent(System.currentTimeMillis(), eventTime, |
328 | KeyEvent.ACTION_UP, keyCode, 0, 0, |
329 | KeyCharacterMap.VIRTUAL_KEYBOARD, 0, |
330 | flags)); |
331 | } |
332 | |
333 | // Calls from Java to C++ |
334 | |
335 | @VisibleForTesting |
336 | boolean checkCompositionQueueAndCallNative(String text, int newCursorPosition, |
337 | boolean isCommit) { |
338 | if (mNativeImeAdapterAndroid == 0) return false; |
339 | |
340 | // Committing an empty string finishes the current composition. |
341 | boolean isFinish = text.isEmpty(); |
342 | mViewEmbedder.onImeEvent(isFinish); |
343 | int keyCode = shouldSendKeyEventWithKeyCode(text); |
344 | long timeStampMs = System.currentTimeMillis(); |
345 | |
346 | if (keyCode != COMPOSITION_KEY_CODE) { |
347 | sendKeyEventWithKeyCode(keyCode, |
348 | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE); |
349 | } else { |
350 | nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDown, |
351 | timeStampMs, keyCode, 0); |
352 | if (isCommit) { |
353 | nativeCommitText(mNativeImeAdapterAndroid, text); |
354 | } else { |
355 | nativeSetComposingText(mNativeImeAdapterAndroid, text, newCursorPosition); |
356 | } |
357 | nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp, |
358 | timeStampMs, keyCode, 0); |
359 | } |
360 | |
361 | return true; |
362 | } |
363 | |
364 | void finishComposingText() { |
365 | if (mNativeImeAdapterAndroid == 0) return; |
366 | nativeFinishComposingText(mNativeImeAdapterAndroid); |
367 | } |
368 | |
369 | boolean translateAndSendNativeEvents(KeyEvent event) { |
370 | if (mNativeImeAdapterAndroid == 0) return false; |
371 | |
372 | int action = event.getAction(); |
373 | if (action != KeyEvent.ACTION_DOWN && |
374 | action != KeyEvent.ACTION_UP) { |
375 | // action == KeyEvent.ACTION_MULTIPLE |
376 | // TODO(bulach): confirm the actual behavior. Apparently: |
377 | // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a |
378 | // composition key down (229) followed by a commit text with the |
379 | // string from event.getUnicodeChars(). |
380 | // Otherwise, we'd need to send an event with a |
381 | // WebInputEvent::IsAutoRepeat modifier. We also need to verify when |
382 | // we receive ACTION_MULTIPLE: we may receive it after an ACTION_DOWN, |
383 | // and if that's the case, we'll need to review when to send the Char |
384 | // event. |
385 | return false; |
386 | } |
387 | mViewEmbedder.onImeEvent(false); |
388 | return nativeSendKeyEvent(mNativeImeAdapterAndroid, event, event.getAction(), |
389 | getModifiers(event.getMetaState()), event.getEventTime(), event.getKeyCode(), |
390 | event.isSystem(), event.getUnicodeChar()); |
391 | } |
392 | |
393 | boolean sendSyntheticKeyEvent( |
394 | int eventType, long timestampMs, int keyCode, int unicodeChar) { |
395 | if (mNativeImeAdapterAndroid == 0) return false; |
396 | |
397 | nativeSendSyntheticKeyEvent( |
398 | mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, unicodeChar); |
399 | return true; |
400 | } |
401 | |
402 | boolean deleteSurroundingText(int leftLength, int rightLength) { |
403 | if (mNativeImeAdapterAndroid == 0) return false; |
404 | nativeDeleteSurroundingText(mNativeImeAdapterAndroid, leftLength, rightLength); |
405 | return true; |
406 | } |
407 | |
408 | @VisibleForTesting |
409 | protected boolean setEditableSelectionOffsets(int start, int end) { |
410 | if (mNativeImeAdapterAndroid == 0) return false; |
411 | nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end); |
412 | return true; |
413 | } |
414 | |
415 | void batchStateChanged(boolean isBegin) { |
416 | if (mNativeImeAdapterAndroid == 0) return; |
417 | nativeImeBatchStateChanged(mNativeImeAdapterAndroid, isBegin); |
418 | } |
419 | |
420 | void commitText() { |
421 | cancelComposition(); |
422 | if (mNativeImeAdapterAndroid != 0) { |
423 | nativeCommitText(mNativeImeAdapterAndroid, ""); |
424 | } |
425 | } |
426 | |
427 | /** |
428 | * Send a request to the native counterpart to set compositing region to given indices. |
429 | * @param start The start of the composition. |
430 | * @param end The end of the composition. |
431 | * @return Whether the native counterpart of ImeAdapter received the call. |
432 | */ |
433 | boolean setComposingRegion(int start, int end) { |
434 | if (mNativeImeAdapterAndroid == 0) return false; |
435 | nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end); |
436 | return true; |
437 | } |
438 | |
439 | /** |
440 | * Send a request to the native counterpart to unselect text. |
441 | * @return Whether the native counterpart of ImeAdapter received the call. |
442 | */ |
443 | public boolean unselect() { |
444 | if (mNativeImeAdapterAndroid == 0) return false; |
445 | nativeUnselect(mNativeImeAdapterAndroid); |
446 | return true; |
447 | } |
448 | |
449 | /** |
450 | * Send a request to the native counterpart of ImeAdapter to select all the text. |
451 | * @return Whether the native counterpart of ImeAdapter received the call. |
452 | */ |
453 | public boolean selectAll() { |
454 | if (mNativeImeAdapterAndroid == 0) return false; |
455 | nativeSelectAll(mNativeImeAdapterAndroid); |
456 | return true; |
457 | } |
458 | |
459 | /** |
460 | * Send a request to the native counterpart of ImeAdapter to cut the selected text. |
461 | * @return Whether the native counterpart of ImeAdapter received the call. |
462 | */ |
463 | public boolean cut() { |
464 | if (mNativeImeAdapterAndroid == 0) return false; |
465 | nativeCut(mNativeImeAdapterAndroid); |
466 | return true; |
467 | } |
468 | |
469 | /** |
470 | * Send a request to the native counterpart of ImeAdapter to copy the selected text. |
471 | * @return Whether the native counterpart of ImeAdapter received the call. |
472 | */ |
473 | public boolean copy() { |
474 | if (mNativeImeAdapterAndroid == 0) return false; |
475 | nativeCopy(mNativeImeAdapterAndroid); |
476 | return true; |
477 | } |
478 | |
479 | /** |
480 | * Send a request to the native counterpart of ImeAdapter to paste the text from the clipboard. |
481 | * @return Whether the native counterpart of ImeAdapter received the call. |
482 | */ |
483 | public boolean paste() { |
484 | if (mNativeImeAdapterAndroid == 0) return false; |
485 | nativePaste(mNativeImeAdapterAndroid); |
486 | return true; |
487 | } |
488 | |
489 | // Calls from C++ to Java |
490 | |
491 | @CalledByNative |
492 | private static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, |
493 | int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, |
494 | int modifierCapsLockOn, int modifierNumLockOn) { |
495 | sEventTypeRawKeyDown = eventTypeRawKeyDown; |
496 | sEventTypeKeyUp = eventTypeKeyUp; |
497 | sEventTypeChar = eventTypeChar; |
498 | sModifierShift = modifierShift; |
499 | sModifierAlt = modifierAlt; |
500 | sModifierCtrl = modifierCtrl; |
501 | sModifierCapsLockOn = modifierCapsLockOn; |
502 | sModifierNumLockOn = modifierNumLockOn; |
503 | } |
504 | |
505 | @CalledByNative |
506 | private static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, |
507 | int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, |
508 | int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, |
509 | int textInputTypeNumber, int textInputTypeDate, int textInputTypeDateTime, |
510 | int textInputTypeDateTimeLocal, int textInputTypeMonth, int textInputTypeTime, |
511 | int textInputTypeWeek, int textInputTypeContentEditable) { |
512 | sTextInputTypeNone = textInputTypeNone; |
513 | sTextInputTypeText = textInputTypeText; |
514 | sTextInputTypeTextArea = textInputTypeTextArea; |
515 | sTextInputTypePassword = textInputTypePassword; |
516 | sTextInputTypeSearch = textInputTypeSearch; |
517 | sTextInputTypeUrl = textInputTypeUrl; |
518 | sTextInputTypeEmail = textInputTypeEmail; |
519 | sTextInputTypeTel = textInputTypeTel; |
520 | sTextInputTypeNumber = textInputTypeNumber; |
521 | sTextInputTypeWeek = textInputTypeWeek; |
522 | sTextInputTypeContentEditable = textInputTypeContentEditable; |
523 | } |
524 | |
525 | @CalledByNative |
526 | private void cancelComposition() { |
527 | if (mInputConnection != null) { |
528 | mInputConnection.restartInput(); |
529 | } |
530 | } |
531 | |
532 | @CalledByNative |
533 | void detach() { |
534 | mNativeImeAdapterAndroid = 0; |
535 | mTextInputType = 0; |
536 | } |
537 | |
538 | private native boolean nativeSendSyntheticKeyEvent(int nativeImeAdapterAndroid, |
539 | int eventType, long timestampMs, int keyCode, int unicodeChar); |
540 | |
541 | private native boolean nativeSendKeyEvent(int nativeImeAdapterAndroid, KeyEvent event, |
542 | int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, |
543 | int unicodeChar); |
544 | |
545 | private native void nativeSetComposingText(int nativeImeAdapterAndroid, String text, |
546 | int newCursorPosition); |
547 | |
548 | private native void nativeCommitText(int nativeImeAdapterAndroid, String text); |
549 | |
550 | private native void nativeFinishComposingText(int nativeImeAdapterAndroid); |
551 | |
552 | private native void nativeAttachImeAdapter(int nativeImeAdapterAndroid); |
553 | |
554 | private native void nativeSetEditableSelectionOffsets(int nativeImeAdapterAndroid, |
555 | int start, int end); |
556 | |
557 | private native void nativeSetComposingRegion(int nativeImeAdapterAndroid, int start, int end); |
558 | |
559 | private native void nativeDeleteSurroundingText(int nativeImeAdapterAndroid, |
560 | int before, int after); |
561 | |
562 | private native void nativeImeBatchStateChanged(int nativeImeAdapterAndroid, boolean isBegin); |
563 | |
564 | private native void nativeUnselect(int nativeImeAdapterAndroid); |
565 | private native void nativeSelectAll(int nativeImeAdapterAndroid); |
566 | private native void nativeCut(int nativeImeAdapterAndroid); |
567 | private native void nativeCopy(int nativeImeAdapterAndroid); |
568 | private native void nativePaste(int nativeImeAdapterAndroid); |
569 | private native void nativeResetImeAdapter(int nativeImeAdapterAndroid); |
570 | } |