| Index: content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..582da88d17f10f58dff3f06169adf132aa5221f0
|
| --- /dev/null
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
|
| @@ -0,0 +1,608 @@
|
| +// Copyright 2016 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.Looper;
|
| +import android.view.KeyCharacterMap;
|
| +import android.view.KeyEvent;
|
| +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 java.util.concurrent.BlockingQueue;
|
| +import java.util.concurrent.LinkedBlockingQueue;
|
| +
|
| +/**
|
| + * An implementation of {@link InputConnection} to communicate with external input method
|
| + * apps. Note that it is running on IME thread (except for constructor and calls from ImeAdapter)
|
| + * such that it does not block UI thread and returns text values immediately after any change
|
| + * to them.
|
| + */
|
| +public class ThreadedInputConnection implements ChromiumBaseInputConnection {
|
| + private static final String TAG = "cr_Ime";
|
| + private static final boolean DEBUG_LOGS = false;
|
| +
|
| + private static final TextInputState UNBLOCKER = new TextInputState(
|
| + "", new Range(0, 0), new Range(-1, -1), false, false /* notFromIme */) {
|
| +
|
| + @Override
|
| + public boolean shouldUnblock() {
|
| + return true;
|
| + }
|
| + };
|
| +
|
| + private final Runnable mProcessPendingInputStatesRunnable = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + processPendingInputStates();
|
| + }
|
| + };
|
| +
|
| + private final Runnable mMoveCursorSelectionEndRunnable = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + TextInputState textInputState = requestAndWaitForTextInputState();
|
| + if (textInputState == null) return;
|
| + Range selection = textInputState.selection();
|
| + setSelection(selection.end(), selection.end());
|
| + }
|
| + };
|
| +
|
| + private final Runnable mRequestTextInputStateUpdate = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + boolean result = mImeAdapter.requestTextInputStateUpdate();
|
| + if (!result) unblockOnUiThread();
|
| + }
|
| + };
|
| +
|
| + private final Runnable mNotifyUserActionRunnable = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.notifyUserAction();
|
| + }
|
| + };
|
| +
|
| + private final Runnable mFinishComposingTextRunnable = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.finishComposingText();
|
| + }
|
| + };
|
| +
|
| + private final ImeAdapter mImeAdapter;
|
| + private final Handler mHandler;
|
| + private int mNumNestedBatchEdits;
|
| +
|
| + // TODO(changwan): check if we can keep a pool of TextInputState to avoid creating
|
| + // a bunch of new objects for each key stroke.
|
| + private final BlockingQueue<TextInputState> mQueue = new LinkedBlockingQueue<>();
|
| + private int mPendingAccent;
|
| +
|
| + ThreadedInputConnection(ImeAdapter imeAdapter, Handler handler) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "constructor");
|
| + ImeUtils.checkOnUiThread();
|
| + mImeAdapter = imeAdapter;
|
| + mHandler = handler;
|
| + }
|
| +
|
| + void initializeOutAttrsOnUiThread(int inputType, int inputFlags, int selectionStart,
|
| + int selectionEnd, EditorInfo outAttrs) {
|
| + ImeUtils.checkOnUiThread();
|
| + mNumNestedBatchEdits = 0;
|
| + mPendingAccent = 0;
|
| + ImeUtils.computeEditorInfo(inputType, inputFlags, selectionStart, selectionEnd, outAttrs);
|
| + if (DEBUG_LOGS) {
|
| + Log.w(TAG, "initializeOutAttrs: " + ImeUtils.getEditorInfoDebugString(outAttrs));
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void updateStateOnUiThread(final String text, final int selectionStart,
|
| + final int selectionEnd, final int compositionStart, final int compositionEnd,
|
| + boolean singleLine, final boolean isNonImeChange) {
|
| + ImeUtils.checkOnUiThread();
|
| +
|
| + final TextInputState newState =
|
| + new TextInputState(text, new Range(selectionStart, selectionEnd),
|
| + new Range(compositionStart, compositionEnd), singleLine, !isNonImeChange);
|
| + if (DEBUG_LOGS) Log.w(TAG, "updateState: %s", newState);
|
| +
|
| + addToQueueOnUiThread(newState);
|
| + if (isNonImeChange) {
|
| + mHandler.post(mProcessPendingInputStatesRunnable);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @see ChromiumBaseInputConnection#getHandler()
|
| + */
|
| + @Override
|
| + public Handler getHandler() {
|
| + return mHandler;
|
| + }
|
| +
|
| + /**
|
| + * @see ChromiumBaseInputConnection#onRestartInputOnUiThread()
|
| + */
|
| + @Override
|
| + public void onRestartInputOnUiThread() {}
|
| +
|
| + /**
|
| + * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent)
|
| + */
|
| + @Override
|
| + public boolean sendKeyEventOnUiThread(final KeyEvent event) {
|
| + ImeUtils.checkOnUiThread();
|
| + mHandler.post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + sendKeyEvent(event);
|
| + }
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see ChromiumBaseInputConnection#moveCursorToSelectionEndOnUiThread()
|
| + */
|
| + @Override
|
| + public void moveCursorToSelectionEndOnUiThread() {
|
| + mHandler.post(mMoveCursorSelectionEndRunnable);
|
| + }
|
| +
|
| + @Override
|
| + @VisibleForTesting
|
| + public void unblockOnUiThread() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "unblockOnUiThread");
|
| + ImeUtils.checkOnUiThread();
|
| + addToQueueOnUiThread(UNBLOCKER);
|
| + mHandler.post(mProcessPendingInputStatesRunnable);
|
| + }
|
| +
|
| + private void processPendingInputStates() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "checkQueue");
|
| + assertOnImeThread();
|
| + // Handle all the remaining states in the queue.
|
| + while (true) {
|
| + TextInputState state = mQueue.poll();
|
| + if (state == null) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "checkQueue - finished");
|
| + return;
|
| + }
|
| + // Unblocker was not used. Ignore.
|
| + if (state.shouldUnblock()) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "checkQueue - ignoring one unblocker");
|
| + continue;
|
| + }
|
| + if (DEBUG_LOGS) Log.w(TAG, "checkQueue: " + state);
|
| + ImeUtils.checkCondition(!state.fromIme());
|
| + updateSelection(state);
|
| + }
|
| + }
|
| +
|
| + private void updateSelection(TextInputState textInputState) {
|
| + if (textInputState == null) return;
|
| + assertOnImeThread();
|
| + if (mNumNestedBatchEdits != 0) return;
|
| + Range selection = textInputState.selection();
|
| + Range composition = textInputState.composition();
|
| + mImeAdapter.updateSelection(
|
| + selection.start(), selection.end(), composition.start(), composition.end());
|
| + }
|
| +
|
| + private TextInputState requestAndWaitForTextInputState() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "requestAndWaitForTextInputState");
|
| + ThreadUtils.postOnUiThread(mRequestTextInputStateUpdate);
|
| + return blockAndGetStateUpdate();
|
| + }
|
| +
|
| + private void addToQueueOnUiThread(TextInputState textInputState) {
|
| + ImeUtils.checkOnUiThread();
|
| + try {
|
| + mQueue.put(textInputState);
|
| + } catch (InterruptedException e) {
|
| + Log.e(TAG, "addToQueueOnUiThread interrupted", e);
|
| + }
|
| + if (DEBUG_LOGS) Log.w(TAG, "addToQueueOnUiThread finished: %d", mQueue.size());
|
| + }
|
| +
|
| + /**
|
| + * @return BlockingQueue for white box unit testing.
|
| + */
|
| + BlockingQueue<TextInputState> getQueueForTest() {
|
| + return mQueue;
|
| + }
|
| +
|
| + private void assertOnImeThread() {
|
| + ImeUtils.checkCondition(mHandler.getLooper() == Looper.myLooper());
|
| + }
|
| +
|
| + /**
|
| + * Block until we get the expected state update.
|
| + * @return TextInputState if we get it successfully. null otherwise.
|
| + */
|
| + private TextInputState blockAndGetStateUpdate() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "blockAndGetStateUpdate");
|
| + assertOnImeThread();
|
| + boolean shouldUpdateSelection = false;
|
| + while (true) {
|
| + TextInputState state;
|
| + try {
|
| + state = mQueue.take();
|
| + } catch (InterruptedException e) {
|
| + // This should never happen since IME thread is artificial and is not exposed
|
| + // to other components.
|
| + e.printStackTrace();
|
| + ImeUtils.checkCondition(false);
|
| + return null;
|
| + }
|
| + if (state.shouldUnblock()) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "blockAndGetStateUpdate: unblocked");
|
| + return null;
|
| + } else if (state.fromIme()) {
|
| + if (shouldUpdateSelection) updateSelection(state);
|
| + if (DEBUG_LOGS) Log.w(TAG, "blockAndGetStateUpdate done: %d", mQueue.size());
|
| + return state;
|
| + }
|
| + // Ignore when state is not from IME, but make sure to update state when we handle
|
| + // state from IME.
|
| + shouldUpdateSelection = true;
|
| + }
|
| + }
|
| +
|
| + private void notifyUserAction() {
|
| + ThreadUtils.postOnUiThread(mNotifyUserActionRunnable);
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#setComposingText(java.lang.CharSequence, int)
|
| + */
|
| + @Override
|
| + public boolean setComposingText(final CharSequence text, final int newCursorPosition) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "setComposingText [%s] [%d]", text, newCursorPosition);
|
| + assertOnImeThread();
|
| + cancelCombiningAccent();
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.sendCompositionToNative(text, newCursorPosition, false);
|
| + }
|
| + });
|
| + notifyUserAction();
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#commitText(java.lang.CharSequence, int)
|
| + */
|
| + @Override
|
| + public boolean commitText(final CharSequence text, final int newCursorPosition) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "commitText [%s] [%d]", text, newCursorPosition);
|
| + assertOnImeThread();
|
| + cancelCombiningAccent();
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.sendCompositionToNative(text, newCursorPosition, text.length() > 0);
|
| + }
|
| + });
|
| + notifyUserAction();
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#performEditorAction(int)
|
| + */
|
| + @Override
|
| + public boolean performEditorAction(final int actionCode) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "performEditorAction [%d]", actionCode);
|
| + assertOnImeThread();
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.performEditorAction(actionCode);
|
| + }
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#performContextMenuAction(int)
|
| + */
|
| + @Override
|
| + public boolean performContextMenuAction(final int id) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "performContextMenuAction [%d]", id);
|
| + assertOnImeThread();
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.performContextMenuAction(id);
|
| + }
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#getExtractedText(android.view.inputmethod.ExtractedTextRequest, int)
|
| + */
|
| + @Override
|
| + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "getExtractedText");
|
| + assertOnImeThread();
|
| + TextInputState textInputState = requestAndWaitForTextInputState();
|
| + 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 = textInputState.singleLine() ? ExtractedText.FLAG_SINGLE_LINE : 0;
|
| + return extractedText;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#beginBatchEdit()
|
| + */
|
| + @Override
|
| + public boolean beginBatchEdit() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0));
|
| + assertOnImeThread();
|
| + mNumNestedBatchEdits++;
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#endBatchEdit()
|
| + */
|
| + @Override
|
| + public boolean endBatchEdit() {
|
| + assertOnImeThread();
|
| + if (mNumNestedBatchEdits == 0) return false;
|
| + --mNumNestedBatchEdits;
|
| + if (DEBUG_LOGS) Log.w(TAG, "endBatchEdit [%b]", (mNumNestedBatchEdits == 0));
|
| + if (mNumNestedBatchEdits == 0) {
|
| + updateSelection(requestAndWaitForTextInputState());
|
| + }
|
| + return mNumNestedBatchEdits != 0;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#deleteSurroundingText(int, int)
|
| + */
|
| + @Override
|
| + public boolean deleteSurroundingText(final int beforeLength, final int afterLength) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength);
|
| + assertOnImeThread();
|
| + if (mPendingAccent != 0) {
|
| + finishComposingText();
|
| + }
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
|
| + }
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#sendKeyEvent(android.view.KeyEvent)
|
| + */
|
| + @Override
|
| + public boolean sendKeyEvent(final KeyEvent event) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode());
|
| + assertOnImeThread();
|
| +
|
| + if (handleCombiningAccent(event)) return true;
|
| +
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.sendKeyEvent(event);
|
| + }
|
| + });
|
| + notifyUserAction();
|
| + return true;
|
| + }
|
| +
|
| + private boolean handleCombiningAccent(final KeyEvent event) {
|
| + // TODO(changwan): this will break the current composition. check if we can
|
| + // implement it in the renderer instead.
|
| + int action = event.getAction();
|
| + int unicodeChar = event.getUnicodeChar();
|
| +
|
| + if (action != KeyEvent.ACTION_DOWN) return false;
|
| + if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
|
| + int pendingAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK;
|
| + StringBuilder builder = new StringBuilder();
|
| + builder.appendCodePoint(pendingAccent);
|
| + setComposingText(builder.toString(), 1);
|
| + mPendingAccent = pendingAccent;
|
| + return true;
|
| + } else if (mPendingAccent != 0 && unicodeChar != 0) {
|
| + int combined = KeyEvent.getDeadChar(mPendingAccent, unicodeChar);
|
| + if (combined != 0) {
|
| + StringBuilder builder = new StringBuilder();
|
| + builder.appendCodePoint(combined);
|
| + commitText(builder.toString(), 1);
|
| + return true;
|
| + }
|
| + // Noncombinable character; commit the accent character and fall through to sending
|
| + // the key event for the character afterwards.
|
| + finishComposingText();
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + private void cancelCombiningAccent() {
|
| + mPendingAccent = 0;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#finishComposingText()
|
| + */
|
| + @Override
|
| + public boolean finishComposingText() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "finishComposingText");
|
| + cancelCombiningAccent();
|
| + // This is the only function that may be called on UI thread because
|
| + // of direct calls from InputMethodManager.
|
| + ThreadUtils.postOnUiThread(mFinishComposingTextRunnable);
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#setSelection(int, int)
|
| + */
|
| + @Override
|
| + public boolean setSelection(final int start, final int end) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "setSelection [%d %d]", start, end);
|
| + assertOnImeThread();
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.setEditableSelectionOffsets(start, end);
|
| + }
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#setComposingRegion(int, int)
|
| + */
|
| + @Override
|
| + public boolean setComposingRegion(final int start, final int end) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "setComposingRegion [%d %d]", start, end);
|
| + assertOnImeThread();
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mImeAdapter.setComposingRegion(start, end);
|
| + }
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#getTextBeforeCursor(int, int)
|
| + */
|
| + @Override
|
| + public CharSequence getTextBeforeCursor(int maxChars, int flags) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "getTextBeforeCursor [%d %x]", maxChars, flags);
|
| + assertOnImeThread();
|
| + TextInputState textInputState = requestAndWaitForTextInputState();
|
| + if (textInputState == null) return null;
|
| + return textInputState.getTextBeforeSelection(maxChars);
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#getTextAfterCursor(int, int)
|
| + */
|
| + @Override
|
| + public CharSequence getTextAfterCursor(int maxChars, int flags) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "getTextAfterCursor [%d %x]", maxChars, flags);
|
| + assertOnImeThread();
|
| + TextInputState textInputState = requestAndWaitForTextInputState();
|
| + if (textInputState == null) return null;
|
| + return textInputState.getTextAfterSelection(maxChars);
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#getSelectedText(int)
|
| + */
|
| + @Override
|
| + public CharSequence getSelectedText(int flags) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "getSelectedText [%x]", flags);
|
| + assertOnImeThread();
|
| + TextInputState textInputState = requestAndWaitForTextInputState();
|
| + if (textInputState == null) return null;
|
| + return textInputState.getSelectedText();
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#getCursorCapsMode(int)
|
| + */
|
| + @Override
|
| + public int getCursorCapsMode(int reqModes) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "getCursorCapsMode [%x]", reqModes);
|
| + assertOnImeThread();
|
| + // TODO(changwan): implement this.
|
| + return 0;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#commitCompletion(android.view.inputmethod.CompletionInfo)
|
| + */
|
| + @Override
|
| + public boolean commitCompletion(CompletionInfo text) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "commitCompletion [%s]", text);
|
| + assertOnImeThread();
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#commitCorrection(android.view.inputmethod.CorrectionInfo)
|
| + */
|
| + @Override
|
| + public boolean commitCorrection(CorrectionInfo correctionInfo) {
|
| + if (DEBUG_LOGS) {
|
| + Log.w(TAG, "commitCorrection [%s]", ImeUtils.getCorrectInfoDebugString(correctionInfo));
|
| + }
|
| + assertOnImeThread();
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#clearMetaKeyStates(int)
|
| + */
|
| + @Override
|
| + public boolean clearMetaKeyStates(int states) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "clearMetaKeyStates [%x]", states);
|
| + assertOnImeThread();
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#reportFullscreenMode(boolean)
|
| + */
|
| + @Override
|
| + public boolean reportFullscreenMode(boolean enabled) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "reportFullscreenMode [%b]", enabled);
|
| + // We ignore fullscreen mode for now. That's why we set
|
| + // EditorInfo.IME_FLAG_NO_FULLSCREEN in constructor.
|
| + // Note that this may be called on UI thread.
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#performPrivateCommand(java.lang.String, android.os.Bundle)
|
| + */
|
| + @Override
|
| + public boolean performPrivateCommand(String action, Bundle data) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "performPrivateCommand [%s]", action);
|
| + assertOnImeThread();
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @see InputConnection#requestCursorUpdates(int)
|
| + */
|
| + @Override
|
| + public boolean requestCursorUpdates(int cursorUpdateMode) {
|
| + if (DEBUG_LOGS) Log.w(TAG, "requestCursorUpdates [%x]", cursorUpdateMode);
|
| + assertOnImeThread();
|
| + return false;
|
| + }
|
| +}
|
|
|