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