Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnection.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnection.java b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnection.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..bad967608b16f67febaed3f327fe24a84f61f6bf |
| --- /dev/null |
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnection.java |
| @@ -0,0 +1,735 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.content.browser.input; |
| + |
| +import android.os.Bundle; |
| +import android.os.Handler; |
| +import android.os.SystemClock; |
| +import android.text.InputType; |
| +import android.view.KeyEvent; |
| +import android.view.View; |
| +import android.view.inputmethod.CompletionInfo; |
| +import android.view.inputmethod.CorrectionInfo; |
| +import android.view.inputmethod.EditorInfo; |
| +import android.view.inputmethod.ExtractedText; |
| +import android.view.inputmethod.ExtractedTextRequest; |
| +import android.view.inputmethod.InputConnection; |
| + |
| +import org.chromium.base.Log; |
| +import org.chromium.base.ThreadUtils; |
| +import org.chromium.base.VisibleForTesting; |
| +import org.chromium.blink_public.web.WebInputEventType; |
| +import org.chromium.blink_public.web.WebTextInputFlags; |
| +import org.chromium.ui.base.ime.TextInputType; |
| + |
| +import java.util.concurrent.BlockingQueue; |
| +import java.util.concurrent.Callable; |
| +import java.util.concurrent.ExecutionException; |
| +import java.util.concurrent.FutureTask; |
| +import java.util.concurrent.LinkedBlockingQueue; |
| +import java.util.concurrent.TimeUnit; |
| +import java.util.concurrent.atomic.AtomicBoolean; |
| + |
| +/** |
| + * TODO(changwan): add a description |
| + */ |
| +public class ChromiumInputConnection implements ChromiumBaseInputConnection { |
| + private static final String TAG = "cr.Ime"; |
| + |
| + // Android will raise TimeoutException to IME if bound call takes 2 seconds. |
| + private static final long MAX_TIMEOUT = 1900; |
| + private static final long MIN_TIMEOUT = 15; |
| + |
| + private final Handler mHandler; |
| + private final View mInternalView; |
| + private final ImeAdapter mImeAdapter; |
| + |
| + private int mNumNestedBatchEdits = 0; |
| + |
| + // TODO(changwan): this value is used in two threads. |
| + private final AtomicBoolean mSingleLine = new AtomicBoolean(true); |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
How about making this one of the TextInputState fi
Changwan Ryu
2016/01/19 07:31:53
Good idea! Done.
|
| + private final BlockingQueue<TextInputState> mQueue = new LinkedBlockingQueue<>(); |
| + private TextInputState mLastTextInputState; |
| + |
| + @VisibleForTesting |
| + ChromiumInputConnection(View view, ImeAdapter imeAdapter) { |
| + ImeUtils.assertOnUiThread(); |
| + |
| + mHandler = InputConnectionHandlerFactory.getInputConnectionHandler(); |
| + mInternalView = view; |
| + mImeAdapter = imeAdapter; |
| + mImeAdapter.setInputConnection(ChromiumInputConnection.this); |
| + |
| + // Make sure the previous composition is finished. There is no proper way to |
| + // tell IME about the previous composition by the time this call ends, anyways. |
| + // finishComposingText(); |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
Can this be deleted? Was this useful for anything
Changwan Ryu
2016/01/19 07:31:53
Done.
|
| + |
| + Log.d(TAG, "%d Constructor called", System.identityHashCode(this)); |
| + } |
| + |
| + private void updateToInputMethodManager(TextInputState textInputState) { |
| + ImeUtils.assertNotOnUiThread(); |
| + if (mNumNestedBatchEdits != 0) { |
| + mLastTextInputState = textInputState; |
| + return; |
| + } |
| + ImeUtils.assertReally(textInputState != null); |
| + if (textInputState.equals(mLastTextInputState)) return; |
| + Range selection = textInputState.selection(); |
| + Range composition = textInputState.composition(); |
| + getInputMethodManagerWrapper().updateSelection(mInternalView, selection.start(), |
| + selection.end(), composition.start(), composition.end()); |
| + mLastTextInputState = textInputState; |
| + } |
| + |
| + public void updateEditorInfo(EditorInfo outAttrs, int inputType, int inputFlags) { |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
Please rename this to updateEditorInfoOnUiThread a
Changwan Ryu
2016/01/19 07:31:53
Done.
|
| + Log.d(TAG, "updateEditorInfo"); |
| + mSingleLine.set(true); |
| + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI; |
| + outAttrs.inputType = |
| + EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; |
| + |
| + if ((inputFlags & WebTextInputFlags.AutocompleteOff) != 0) { |
| + outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS; |
| + } |
| + |
| + if (inputType == TextInputType.TEXT) { |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
Please move most of this logic into a private stat
Changwan Ryu
2016/01/19 07:31:53
Done.
|
| + // Normal text field |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| + if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { |
| + outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; |
| + } |
| + } else if (inputType == TextInputType.TEXT_AREA |
| + || inputType == TextInputType.CONTENT_EDITABLE) { |
| + outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; |
| + mSingleLine.set(false); |
| + if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { |
| + outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; |
| + } |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE; |
| + } else if (inputType == TextInputType.PASSWORD) { |
| + // Password |
| + outAttrs.inputType = |
| + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| + } else if (inputType == TextInputType.SEARCH) { |
| + // Search |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH; |
| + } else if (inputType == TextInputType.URL) { |
| + // Url |
| + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI; |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| + } else if (inputType == TextInputType.EMAIL) { |
| + outAttrs.inputType = |
| + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| + } else if (inputType == TextInputType.TELEPHONE) { |
| + // Telephone |
| + // Number and telephone do not have both a Tab key and an |
| + // action in default OSK, so set the action to NEXT |
| + outAttrs.inputType = InputType.TYPE_CLASS_PHONE; |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; |
| + } else if (inputType == TextInputType.NUMBER) { |
| + // Number |
| + outAttrs.inputType = InputType.TYPE_CLASS_NUMBER |
| + | InputType.TYPE_NUMBER_VARIATION_NORMAL | InputType.TYPE_NUMBER_FLAG_DECIMAL; |
| + outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; |
| + } |
| + |
| + // Handling of autocapitalize. Blink will send the flag taking into account the element's |
| + // type. This is not using AutocapitalizeNone because Android does not autocapitalize by |
| + // default and there is no way to express no capitalization. |
| + // Autocapitalize is meant as a hint to the virtual keyboard. |
| + if ((inputFlags & WebTextInputFlags.AutocapitalizeCharacters) != 0) { |
| + outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; |
| + } else if ((inputFlags & WebTextInputFlags.AutocapitalizeWords) != 0) { |
| + outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS; |
| + } else if ((inputFlags & WebTextInputFlags.AutocapitalizeSentences) != 0) { |
| + outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; |
| + } |
| + // Content editable doesn't use autocapitalize so we need to set it manually. |
| + if (inputType == TextInputType.CONTENT_EDITABLE) { |
| + outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; |
| + } |
| + |
| + Range selection = getSelection(); |
| + outAttrs.initialSelStart = selection.start(); |
| + outAttrs.initialSelEnd = selection.end(); |
| + } |
| + |
| + @Override |
| + public void updateState(final String text, final int selectionStart, final int selectionEnd, |
| + final int compositionStart, final int compositionEnd, final boolean isNonImeChange) { |
| + Log.d(TAG, "updateState [%s] [%s %s] [%s %s] [%b]", text, selectionStart, selectionEnd, |
| + compositionStart, compositionEnd, isNonImeChange); |
| + ImeUtils.assertOnUiThread(); |
| + |
| + // TODO(changwan): this should be called at the end of each call, not after it. |
| + final TextInputState newState = |
| + new TextInputState(text, new Range(selectionStart, selectionEnd), |
| + new Range(compositionStart, compositionEnd)); |
| + |
| + if (isNonImeChange) { |
| + mHandler.post(new Runnable() { |
| + @Override |
| + public void run() { |
| + updateToInputMethodManager(newState); |
| + } |
| + }); |
| + } else { |
| + onStateUpdateOriginatingFromIme(newState); |
| + } |
| + Log.d(TAG, "updateState finished"); |
| + } |
| + |
| + private TextInputState updateTextInputState() { |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.requestTextInputStateUpdate(); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate(); |
| + } |
| + |
| + private Range getSelection() { |
| + Log.d(TAG, "getSelection"); |
| + return new Range(0, 0); |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
Looks like this needs to be fixed to work properly
Changwan Ryu
2016/01/19 07:31:53
Applied the logic from AdapterInputConnection.
BT
|
| + // TextInputState textInputState = updateTextInputState(); |
| + // if (textInputState == null) return new Range(0, 0); |
| + // return textInputState.selection(); |
| + } |
| + |
| + private void onStateUpdateOriginatingFromIme(TextInputState textInputState) { |
| + ImeUtils.assertOnUiThread(); |
| + try { |
| + mQueue.put(textInputState); |
| + } catch (InterruptedException e) { |
| + Log.w(TAG, "onStateUpdateOriginatingFromIme interrupted", e); |
| + } |
| + Log.d(TAG, "%d onStateUpdateOriginatingFromIme finished: %d", System.identityHashCode(this), |
| + mQueue.size()); |
| + } |
| + |
| + /** |
| + * TODO(changwan): simplify calls using this. |
| + * @param c |
| + * @return |
| + */ |
| + private TextInputState runAndGetStateUpdate(final Callable<Boolean> c) { |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + // TODO(changwan): remove this. |
| + Boolean result = null; |
| + try { |
| + result = c.call(); |
| + } catch (Exception e) { |
| + e.printStackTrace(); |
| + } |
| + if (result == null || !result.booleanValue()) { |
| + // Now we know that updateState() will never be called. |
| + unblock(); |
| + } |
| + } |
| + }); |
| + |
| + return blockAndGetStateUpdate(); |
| + } |
| + |
| + /** |
| + * TODO(changwan): simplify calls using this. |
| + * @param c |
| + * @return |
| + */ |
| + private TextInputState runAndGetStateUpdate2(final Callable<Boolean> c) { |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
This has no callers, can it be deleted?
Changwan Ryu
2016/01/19 07:31:53
Done.
|
| + mQueue.clear(); |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + Boolean result = null; |
| + try { |
| + result = c.call(); |
| + } catch (Exception e) { |
| + e.printStackTrace(); |
| + } |
| + if (result == null || !result.booleanValue()) { |
| + // Now we know that updateState() will never be called. |
| + unblock(); |
| + } |
| + } |
| + }); |
| + FutureTask<TextInputState> task = new FutureTask<>(new Callable<TextInputState>() { |
| + @Override |
| + public TextInputState call() throws Exception { |
| + TextInputState result = blockAndGetStateUpdate(); |
| + // if (result != null) updateToInputMethodManager(result); |
| + return result; |
| + } |
| + }); |
| + mHandler.post(task); |
| + if (!ThreadUtils.runningOnUiThread()) { |
| + try { |
| + return task.get(); |
| + } catch (InterruptedException | ExecutionException e) { |
| + // TODO(changwan): Auto-generated catch block |
| + e.printStackTrace(); |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + /** |
| + * Unblock the blockAndGetStateUpdate() function if we found that we will |
| + * never get state update. |
| + */ |
| + private void unblock() { |
| + ImeUtils.assertOnUiThread(); |
| + onStateUpdateOriginatingFromIme(new TextInputState.Dummy()); |
| + } |
| + |
| + private void postConsumeStateUpdate() { |
| + mHandler.post(new Runnable() { |
| + @Override |
| + public void run() { |
| + blockAndGetStateUpdate(); |
| + } |
| + }); |
| + } |
| + |
| + /** |
| + * Block until we get the expected state update. |
| + * @return TextInputState if we get it successfully. null otherwise. |
| + */ |
| + private TextInputState blockAndGetStateUpdate() { |
| + ImeUtils.assertNotOnUiThread(); |
| + long timeout = MAX_TIMEOUT; |
| + try { |
| + while (true) { |
| + long startTime = System.currentTimeMillis(); |
| + TextInputState result = mQueue.poll(timeout, TimeUnit.MILLISECONDS); |
| + |
| + if (result != null) { |
| + if (result.isDummy()) { |
| + Log.d(TAG, "blockAndGetStateUpdate: failed, got dummy"); |
| + return null; |
| + } |
| + updateToInputMethodManager(result); |
| + return result; |
| + } |
| + |
| + long polledTime = System.currentTimeMillis() - startTime; |
| + timeout -= polledTime + 10; |
| + if (timeout < MIN_TIMEOUT) { |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
This doesn't seem useful, how about simply having
Changwan Ryu
2016/01/19 07:31:53
Done.
|
| + Log.e(TAG, "%d blockAndGetStateUpdate: failed %d", |
| + System.identityHashCode(this), mQueue.size()); |
| + ImeUtils.assertReally(false); |
| + return null; |
| + } |
| + } |
| + } catch (InterruptedException e) { |
| + e.printStackTrace(); |
| + Log.d(TAG, "blockAndGetStateUpdate: failed due to interruption"); |
| + return null; |
| + } |
| + } |
| + |
| + /** |
| + * @see InputConnection#setComposingText(java.lang.CharSequence, int) |
| + */ |
| + @Override |
| + public boolean setComposingText(final CharSequence text, final int newCursorPosition) { |
| + Log.d(TAG, "setComposingText [%s] [%d]", text, newCursorPosition); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
Can all these be removed yet?
Changwan Ryu
2016/01/19 07:31:53
Done.
|
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.checkCompositionQueueAndCallNative( |
| + text, newCursorPosition, false); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + |
| + /** |
| + * @see InputConnection#commitText(java.lang.CharSequence, int) |
| + */ |
| + @Override |
| + public boolean commitText(final CharSequence text, final int newCursorPosition) { |
| + Log.d(TAG, "commitText [%s] [%d]", text, newCursorPosition); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.checkCompositionQueueAndCallNative( |
| + text, newCursorPosition, text.length() > 0); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + |
| + @Override |
| + public void restartInput() { |
| + Log.d(TAG, "restartInput"); |
| + if (ThreadUtils.runningOnUiThread()) { |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:03
After this is renamed restartInputOnUiThread, this
Changwan Ryu
2016/01/19 07:31:53
The logic here has been refactored into ImeAdapter
|
| + unblock(); |
| + mHandler.post(new Runnable() { |
| + @Override |
| + public void run() { |
| + getInputMethodManagerWrapper().restartInput(mInternalView); |
| + } |
| + }); |
| + } else { |
| + getInputMethodManagerWrapper().restartInput(mInternalView); |
| + } |
| + } |
| + |
| + /** |
| + * @see InputConnection#performEditorAction(int) |
| + */ |
| + @Override |
| + public boolean performEditorAction(int actionCode) { |
| + Log.d(TAG, "performEditorAction [%d]", actionCode); |
| + ImeUtils.assertNotOnUiThread(); |
| + if (actionCode == EditorInfo.IME_ACTION_NEXT) { |
| + restartInput(); |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
This is the only IME-thread caller of restartInput
Changwan Ryu
2016/01/19 07:31:53
Thanks for pointing that out. It's already been mo
|
| + mQueue.clear(); // TODO(changwan): remove |
| + // Send TAB key event |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + long timeStampMs = SystemClock.uptimeMillis(); |
| + boolean result = mImeAdapter.sendSyntheticKeyEvent( |
| + WebInputEventType.RawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0, 0); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } else { |
| + mQueue.clear(); // TODO(changwan): remove |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.sendKeyEventWithKeyCode( |
| + KeyEvent.KEYCODE_ENTER, KeyEvent.FLAG_SOFT_KEYBOARD |
| + | KeyEvent.FLAG_KEEP_TOUCH_MODE | KeyEvent.FLAG_EDITOR_ACTION); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + } |
| + |
| + /** |
| + * @see InputConnection#performContextMenuAction(int) |
| + */ |
| + @Override |
| + public boolean performContextMenuAction(final int id) { |
| + Log.d(TAG, "performContextMenuAction [%d]", id); |
| + ImeUtils.assertNotOnUiThread(); |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + mImeAdapter.performContextMenuAction(id); |
| + } |
| + }); |
| + // TODO(changwan): return correct value |
| + return true; |
| + } |
| + |
| + /** |
| + * @see InputConnection#getExtractedText(android.view.inputmethod.ExtractedTextRequest, |
| + * int) |
| + */ |
| + @Override |
| + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { |
| + Log.d(TAG, "getExtractedText"); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + TextInputState textInputState = updateTextInputState(); |
| + if (textInputState == null) return null; |
| + ExtractedText extractedText = new ExtractedText(); |
| + extractedText.text = textInputState.text(); |
| + extractedText.partialEndOffset = textInputState.text().length(); |
| + extractedText.selectionStart = textInputState.selection().start(); |
| + extractedText.selectionEnd = textInputState.selection().end(); |
| + extractedText.flags = mSingleLine.get() ? ExtractedText.FLAG_SINGLE_LINE : 0; |
| + return extractedText; |
| + } |
| + |
| + /** |
| + * @see InputConnection#beginBatchEdit() |
| + */ |
| + @Override |
| + public boolean beginBatchEdit() { |
| + Log.d(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0)); |
| + ImeUtils.assertNotOnUiThread(); |
| + mNumNestedBatchEdits++; |
| + return true; |
| + } |
| + |
| + /** |
| + * @see InputConnection#endBatchEdit() |
| + */ |
| + @Override |
| + public boolean endBatchEdit() { |
| + ImeUtils.assertNotOnUiThread(); |
| + if (mNumNestedBatchEdits == 0) return false; |
| + --mNumNestedBatchEdits; |
| + Log.d(TAG, "endBatchEdit [%b]", (mNumNestedBatchEdits == 0)); |
| + if (mNumNestedBatchEdits == 0 && mLastTextInputState != null) { |
| + updateToInputMethodManager(mLastTextInputState); |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
I don't think this works as you intended, this is
Changwan Ryu
2016/01/19 07:31:53
You're correct. Now we have mLastUpdatedTextInputS
|
| + } |
| + return mNumNestedBatchEdits != 0; |
| + } |
| + |
| + /** |
| + * @see InputConnection#deleteSurroundingText(int, int) |
| + */ |
| + @Override |
| + public boolean deleteSurroundingText(final int beforeLength, final int afterLength) { |
| + Log.d(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.deleteSurroundingText(beforeLength, afterLength); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + |
| + /** |
| + * @see ChromiumBaseInputConnection#dispatchKeyEvent(KeyEvent) |
| + */ |
| + @Override |
| + public boolean dispatchKeyEvent(final KeyEvent event) { |
| + // TODO(changwan): check if we need to post to IME handler first. |
| + ImeUtils.assertOnUiThread(); |
| + // This should not happen. |
| + if (!mImeAdapter.translateAndSendNativeEvents(event)) return false; |
| + postConsumeStateUpdate(); |
| + return true; |
| + } |
| + |
| + /** |
| + * @see InputConnection#sendKeyEvent(android.view.KeyEvent) |
| + */ |
| + @Override |
| + public boolean sendKeyEvent(final KeyEvent event) { |
| + Log.d(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode()); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.translateAndSendNativeEvents(event); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + |
| + /** |
| + * @see InputConnection#finishComposingText() |
| + */ |
| + @Override |
| + public boolean finishComposingText() { |
| + Log.d(TAG, "%d finishComposingText", System.identityHashCode(this)); |
| + mQueue.clear(); // TODO(changwan): remove |
| + // This is the only function that may be called on UI thread because |
| + // of direct calls from InputMethodManager. |
| + |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.finishComposingText(); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + |
| + if (ThreadUtils.runningOnUiThread()) { |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
There doesn't appear to be any (non-unit-test) cod
Changwan Ryu
2016/01/19 07:31:53
Hmm.. How about InputMethodManager#reportFinishInp
|
| + postConsumeStateUpdate(); |
| + return true; |
| + } else { |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + } |
| + |
| + /** |
| + * @see InputConnection#setSelection(int, int) |
| + */ |
| + @Override |
| + public boolean setSelection(final int start, final int end) { |
| + Log.d(TAG, "setSelection [%d %d]", start, end); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.setEditableSelectionOffsets(start, end); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + |
| + /** |
| + * @see InputConnection#setComposingRegion(int, int) |
| + */ |
| + @Override |
| + public boolean setComposingRegion(final int start, final int end) { |
| + Log.d(TAG, "setComposingRegion [%d %d]", start, end); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + boolean result = mImeAdapter.setComposingRegion("", start, end); |
| + if (!result) unblock(); |
| + } |
| + }); |
| + return blockAndGetStateUpdate() != null; |
| + } |
| + |
| + private InputMethodManagerWrapper getInputMethodManagerWrapper() { |
| + return mImeAdapter.getInputMethodManagerWrapper(); |
| + } |
| + |
| + /** |
| + * @see InputConnection#getTextBeforeCursor(int, int) |
| + */ |
| + @Override |
| + public CharSequence getTextBeforeCursor(int maxChars, int flags) { |
| + Log.d(TAG, "getTextBeforeCursor [%d %x]", maxChars, flags); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + TextInputState textInputState = updateTextInputState(); |
| + if (textInputState == null) return null; |
| + return textInputState.getTextBeforeSelection(maxChars); |
| + } |
| + |
| + /** |
| + * @see InputConnection#getTextAfterCursor(int, int) |
| + */ |
| + @Override |
| + public CharSequence getTextAfterCursor(int maxChars, int flags) { |
| + Log.d(TAG, "getTextAfterCursor [%d %x]", maxChars, flags); |
| + mQueue.clear(); // TODO(changwan): remove |
| + ImeUtils.assertNotOnUiThread(); |
| + TextInputState textInputState = updateTextInputState(); |
| + if (textInputState == null) return null; |
| + return textInputState.getTextAfterSelection(maxChars); |
| + } |
| + |
| + /** |
| + * @see InputConnection#getSelectedText(int) |
| + */ |
| + @Override |
| + public CharSequence getSelectedText(int flags) { |
| + Log.d(TAG, "getSelectedText [%x]", flags); |
| + ImeUtils.assertNotOnUiThread(); |
| + mQueue.clear(); // TODO(changwan): remove |
| + TextInputState textInputState = updateTextInputState(); |
| + if (textInputState == null) return null; |
| + return textInputState.getSelectedText(); |
| + } |
| + |
| + /** |
| + * @see InputConnection#getCursorCapsMode(int) |
| + */ |
| + @Override |
| + public int getCursorCapsMode(int reqModes) { |
| + Log.d(TAG, "getCursorCapsMode [%x]", reqModes); |
| + ImeUtils.assertNotOnUiThread(); |
| + // TODO(changwan): Auto-generated method stub |
|
aelias_OOO_until_Jul13
2015/09/30 00:10:04
Looks like you have several rarely called methods
Changwan Ryu
2016/01/19 07:31:53
Since InputConnection is an interface, we still ne
|
| + return 0; |
| + } |
| + |
| + /** |
| + * @see InputConnection#commitCompletion(android.view.inputmethod.CompletionInfo) |
| + */ |
| + @Override |
| + public boolean commitCompletion(CompletionInfo text) { |
| + Log.d(TAG, "commitCompletion [%s]", text); |
| + ImeUtils.assertNotOnUiThread(); |
| + // TODO(changwan): Auto-generated method stub |
| + return false; |
| + } |
| + |
| + /** |
| + * @see InputConnection#commitCorrection(android.view.inputmethod.CorrectionInfo) |
| + */ |
| + @Override |
| + public boolean commitCorrection(CorrectionInfo correctionInfo) { |
| + Log.d(TAG, "commitCorrection [%s]", ImeUtils.dumpCorrectionInfo(correctionInfo)); |
| + ImeUtils.assertNotOnUiThread(); |
| + // TODO(changwan): Auto-generated method stub |
| + return false; |
| + } |
| + |
| + /** |
| + * @see InputConnection#clearMetaKeyStates(int) |
| + */ |
| + @Override |
| + public boolean clearMetaKeyStates(int states) { |
| + Log.d(TAG, "clearMetaKeyStates [%x]", states); |
| + ImeUtils.assertNotOnUiThread(); |
| + // TODO(changwan): Auto-generated method stub |
| + return false; |
| + } |
| + |
| + /** |
| + * @see InputConnection#reportFullscreenMode(boolean) |
| + */ |
| + @Override |
| + public boolean reportFullscreenMode(boolean enabled) { |
| + Log.d(TAG, "reportFullscreenMode [%b]", enabled); |
| + ImeUtils.assertNotOnUiThread(); |
| + // We ignore fullscreen mode for now. That's why we set |
| + // EditorInfo.IME_FLAG_NO_FULLSCREEN in constructor. |
| + return false; |
| + } |
| + |
| + /** |
| + * @see InputConnection#performPrivateCommand(java.lang.String, android.os.Bundle) |
| + */ |
| + @Override |
| + public boolean performPrivateCommand(String action, Bundle data) { |
| + Log.d(TAG, "performPrivateCommand [%s]", action); |
| + ImeUtils.assertNotOnUiThread(); |
| + // TODO(changwan): Auto-generated method stub |
| + return false; |
| + } |
| + |
| + /** |
| + * @see InputConnection#requestCursorUpdates(int) |
| + */ |
| + @Override |
| + public boolean requestCursorUpdates(int cursorUpdateMode) { |
| + Log.d(TAG, "requestCursorUpdates [%x]", cursorUpdateMode); |
| + ImeUtils.assertNotOnUiThread(); |
| + // TODO(changwan): Auto-generated method stub |
| + return false; |
| + } |
| + |
| + /** |
| + * TODO(changwan): add description |
| + */ |
| + public void reset() { |
| + ImeUtils.assertOnUiThread(); |
| + finishComposingText(); |
| + } |
| +} |