blob: eff2e2b8276da80f950671cdec50f83a8b5e1e93 [file] [log] [blame]
// Copyright 2017 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.chrome.browser.omnibox;
import android.os.Bundle;
import android.text.Editable;
import android.text.Selection;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.BackgroundColorSpan;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputContentInfo;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* An autocomplete model that appends autocomplete text at the end of query/URL text as
* SpannableString. By wrapping all the keyboard related operations in a batch edit, we can
* effectively hide the existence of autocomplete text from keyboard.
*/
public class SpannableAutocompleteEditTextModel implements AutocompleteEditTextModelBase {
private static final String TAG = "cr_SpanAutocomplete";
private static final boolean DEBUG = false;
// A pattern that matches strings consisting of English and European character sets, numbers,
// punctuations, and a white space.
private static final Pattern NON_COMPOSITIONAL_TEXT_PATTERN = Pattern.compile(
"[\\p{script=latin}\\p{script=cyrillic}\\p{script=greek}\\p{script=hebrew}\\p{Punct} "
+ "0-9]*");
private final AutocompleteEditTextModelBase.Delegate mDelegate;
// The current state that reflects EditText view's current state through callbacks such as
// onSelectionChanged() and onTextChanged(). It reflects all the latest changes even in a batch
// edit. The autocomplete text here is meaningless in the middle of a change.
private final AutocompleteState mCurrentState;
// This keeps track of the state in which previous notification was sent. It prevents redundant
// or unnecessary notification.
private final AutocompleteState mPreviouslyNotifiedState;
// This keeps track of the autocompletetext that we need to show (at the end of batch edit if
// we are in the middle of it), and also the matching user text that it should be appended to.
// Note that this potentially allows the controller to update in a delayed manner.
private final AutocompleteState mPreviouslySetState;
private final SpanCursorController mSpanCursorController;
private AutocompleteInputConnection mInputConnection;
private boolean mLastEditWasTyping = true;
private boolean mIgnoreTextChangeFromAutocomplete = true;
private int mBatchEditNestCount;
private int mDeletePostfixOnNextBeginImeCommand;
// For testing.
private int mLastUpdateSelStart;
private int mLastUpdateSelEnd;
public SpannableAutocompleteEditTextModel(AutocompleteEditTextModelBase.Delegate delegate) {
if (DEBUG) Log.i(TAG, "constructor");
mDelegate = delegate;
mCurrentState = new AutocompleteState(delegate.getText().toString(), "",
delegate.getSelectionStart(), delegate.getSelectionEnd());
mPreviouslyNotifiedState = new AutocompleteState(mCurrentState);
mPreviouslySetState = new AutocompleteState(mCurrentState);
mSpanCursorController = new SpanCursorController(delegate);
}
@Override
public InputConnection onCreateInputConnection(InputConnection inputConnection) {
mLastUpdateSelStart = mDelegate.getSelectionStart();
mLastUpdateSelEnd = mDelegate.getSelectionEnd();
mBatchEditNestCount = 0;
if (inputConnection == null) {
if (DEBUG) Log.i(TAG, "onCreateInputConnection: null");
mInputConnection = null;
return null;
}
if (DEBUG) Log.i(TAG, "onCreateInputConnection");
mInputConnection = new AutocompleteInputConnection();
mInputConnection.setTarget(inputConnection);
return mInputConnection;
}
/**
* @param editable The editable.
* @return Debug string for the given {@Editable}.
*/
private static String getEditableDebugString(Editable editable) {
return String.format(Locale.US, "Editable {[%s] SEL[%d %d] COM[%d %d]}",
editable.toString(), Selection.getSelectionStart(editable),
Selection.getSelectionEnd(editable),
BaseInputConnection.getComposingSpanStart(editable),
BaseInputConnection.getComposingSpanEnd(editable));
}
private void sendAccessibilityEventForUserTextChange(
AutocompleteState oldState, AutocompleteState newState) {
int addedCount = -1;
int removedCount = -1;
int fromIndex = -1;
if (newState.isBackwardDeletedFrom(oldState)) {
addedCount = 0;
removedCount = oldState.getText().length() - newState.getUserText().length();
fromIndex = newState.getUserText().length();
} else if (newState.isForwardTypedFrom(oldState)) {
addedCount = newState.getUserText().length() - oldState.getUserText().length();
removedCount = oldState.getAutocompleteText().length();
fromIndex = oldState.getUserText().length();
} else if (newState.getUserText().equals(oldState.getUserText())) {
addedCount = 0;
removedCount = oldState.getAutocompleteText().length();
fromIndex = oldState.getUserText().length();
} else {
// Assume that the whole text has been replaced.
addedCount = newState.getText().length();
removedCount = oldState.getUserText().length();
fromIndex = 0;
}
if (!oldState.getText().equals(newState.getText())
&& (addedCount != 0 || removedCount != 0)) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setBeforeText(oldState.getText());
event.setFromIndex(fromIndex);
event.setRemovedCount(removedCount);
event.setAddedCount(addedCount);
mDelegate.sendAccessibilityEventUnchecked(event);
}
if (oldState.getSelStart() != newState.getSelEnd()
|| oldState.getSelEnd() != newState.getSelEnd()) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
event.setFromIndex(newState.getSelStart());
event.setToIndex(newState.getSelEnd());
event.setItemCount(newState.getUserText().length());
mDelegate.sendAccessibilityEventUnchecked(event);
}
}
private void sendAccessibilityEventForAppendingAutocomplete(AutocompleteState newState) {
if (!newState.hasAutocompleteText()) return;
// Note that only text changes and selection does not change.
AccessibilityEvent eventTextChanged =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
eventTextChanged.setBeforeText(newState.getUserText());
eventTextChanged.setFromIndex(newState.getUserText().length());
eventTextChanged.setRemovedCount(0);
eventTextChanged.setAddedCount(newState.getAutocompleteText().length());
mDelegate.sendAccessibilityEventUnchecked(eventTextChanged);
}
private void notifyAccessibilityService() {
if (mCurrentState.equals(mPreviouslyNotifiedState)) return;
if (!mDelegate.isAccessibilityEnabled()) return;
sendAccessibilityEventForUserTextChange(mPreviouslyNotifiedState, mCurrentState);
// Read autocomplete text separately.
sendAccessibilityEventForAppendingAutocomplete(mCurrentState);
}
private void notifyAutocompleteTextStateChanged() {
if (DEBUG) {
Log.i(TAG, "notifyAutocompleteTextStateChanged PRV[%s] CUR[%s] IGN[%b]",
mPreviouslyNotifiedState, mCurrentState, mIgnoreTextChangeFromAutocomplete);
}
if (mBatchEditNestCount > 0) {
// crbug.com/764749
Log.w(TAG, "Did not notify - in batch edit.");
return;
}
if (mCurrentState.equals(mPreviouslyNotifiedState)) {
// crbug.com/764749
Log.w(TAG, "Did not notify - no change.");
return;
}
notifyAccessibilityService();
if (mCurrentState.getUserText().equals(mPreviouslyNotifiedState.getUserText())
&& (mCurrentState.hasAutocompleteText()
|| !mPreviouslyNotifiedState.hasAutocompleteText())) {
// Nothing has changed except that autocomplete text has been set or modified. Or
// selection change did not affect autocomplete text. Autocomplete text is set by the
// controller, so only text change or deletion of autocomplete text should be notified.
mPreviouslyNotifiedState.copyFrom(mCurrentState);
return;
}
mPreviouslyNotifiedState.copyFrom(mCurrentState);
if (mIgnoreTextChangeFromAutocomplete) {
// crbug.com/764749
Log.w(TAG, "Did not notify - ignored.");
return;
}
// The current model's mechanism always moves the cursor at the end of user text, so we
// don't need to update the display.
mDelegate.onAutocompleteTextStateChanged(false /* updateDisplay */);
}
private void clearAutocompleteText() {
if (DEBUG) Log.i(TAG, "clearAutocomplete");
mPreviouslySetState.clearAutocompleteText();
mCurrentState.clearAutocompleteText();
}
private void clearAutocompleteTextAndUpdateSpanCursor() {
if (DEBUG) Log.i(TAG, "clearAutocompleteAndUpdateSpanCursor");
clearAutocompleteText();
// Take effect and notify if not already in a batch edit.
if (mInputConnection != null) {
mInputConnection.onBeginImeCommand();
mInputConnection.onEndImeCommand();
} else {
mSpanCursorController.removeSpan();
notifyAutocompleteTextStateChanged();
}
}
@Override
public boolean dispatchKeyEvent(final KeyEvent event) {
if (DEBUG) Log.i(TAG, "dispatchKeyEvent");
if (mInputConnection == null) {
return mDelegate.super_dispatchKeyEvent(event);
}
mInputConnection.onBeginImeCommand();
if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_DOWN) {
mInputConnection.commitAutocomplete();
}
boolean retVal = mDelegate.super_dispatchKeyEvent(event);
mInputConnection.onEndImeCommand();
return retVal;
}
@Override
public void onSetText(CharSequence text) {
if (DEBUG) Log.i(TAG, "onSetText: " + text);
// setText() does not necessarily trigger onTextChanged(). We need to accept the new text
// and reset the states.
mCurrentState.set(text.toString(), "", text.length(), text.length());
mSpanCursorController.reset();
mPreviouslyNotifiedState.copyFrom(mCurrentState);
mPreviouslySetState.copyFrom(mCurrentState);
if (mBatchEditNestCount == 0) updateSelectionForTesting();
}
@Override
public void onSelectionChanged(int selStart, int selEnd) {
if (DEBUG) Log.i(TAG, "onSelectionChanged [%d,%d]", selStart, selEnd);
if (mCurrentState.getSelStart() == selStart && mCurrentState.getSelEnd() == selEnd) return;
mCurrentState.setSelection(selStart, selEnd);
if (mBatchEditNestCount > 0) return;
int len = mCurrentState.getUserText().length();
if (mCurrentState.hasAutocompleteText()) {
if (selStart > len || selEnd > len) {
if (DEBUG) Log.i(TAG, "Autocomplete text is being touched. Make it real.");
if (mInputConnection != null) mInputConnection.commitAutocomplete();
} else {
if (DEBUG) Log.i(TAG, "Touching before the cursor removes autocomplete.");
clearAutocompleteTextAndUpdateSpanCursor();
}
}
notifyAutocompleteTextStateChanged();
updateSelectionForTesting();
}
@Override
public void onFocusChanged(boolean focused) {
if (DEBUG) Log.i(TAG, "onFocusChanged: " + focused);
}
@Override
public void onTextChanged(CharSequence text, int start, int beforeLength, int afterLength) {
if (DEBUG) Log.i(TAG, "onTextChanged: " + text);
mSpanCursorController.reflectTextUpdateInState(mCurrentState, text);
if (mBatchEditNestCount > 0) return; // let endBatchEdit() handles changes from IME.
// An external change such as text paste occurred.
mLastEditWasTyping = false;
clearAutocompleteTextAndUpdateSpanCursor();
}
@Override
public void onPaste() {
if (DEBUG) Log.i(TAG, "onPaste");
}
@Override
public String getTextWithAutocomplete() {
String retVal = mCurrentState.getText();
if (DEBUG) Log.i(TAG, "getTextWithAutocomplete: %s", retVal);
return retVal;
}
@Override
public String getTextWithoutAutocomplete() {
String retVal = mCurrentState.getUserText();
if (DEBUG) Log.i(TAG, "getTextWithoutAutocomplete: " + retVal);
return retVal;
}
@Override
public String getAutocompleteText() {
return mCurrentState.getAutocompleteText();
}
@Override
public void setIgnoreTextChangeFromAutocomplete(boolean ignore) {
if (DEBUG) Log.i(TAG, "setIgnoreText: " + ignore);
mIgnoreTextChangeFromAutocomplete = ignore;
}
@Override
public void setAutocompleteText(CharSequence userText, CharSequence inlineAutocompleteText) {
setAutocompleteTextInternal(userText.toString(), inlineAutocompleteText.toString());
}
private void setAutocompleteTextInternal(String userText, String autocompleteText) {
if (DEBUG) Log.i(TAG, "setAutocompleteText: %s[%s]", userText, autocompleteText);
mPreviouslySetState.set(userText, autocompleteText, userText.length(), userText.length());
// TODO(changwan): avoid any unnecessary removal and addition of autocomplete text when it
// is not changed or when it is appended to the existing autocomplete text.
if (mInputConnection != null) {
mInputConnection.onBeginImeCommand();
mInputConnection.onEndImeCommand();
}
}
@Override
public boolean shouldAutocomplete() {
boolean retVal = mBatchEditNestCount == 0 && mLastEditWasTyping
&& mCurrentState.isCursorAtEndOfUserText() && !isKeyboardBlacklisted()
&& isNonCompositionalText(getTextWithoutAutocomplete());
if (DEBUG) Log.i(TAG, "shouldAutocomplete: " + retVal);
return retVal;
}
private boolean isKeyboardBlacklisted() {
String pkgName = mDelegate.getKeyboardPackageName();
return pkgName.contains(".iqqi") // crbug.com/767016
|| pkgName.contains("omronsoft") || pkgName.contains(".iwnn"); // crbug.com/758443
}
private boolean shouldFinishCompositionOnDeletion() {
// crbug.com/758443, crbug.com/766888: Japanese keyboard does not finish composition when we
// restore the deleted text, and later typing will make Japanese keyboard move before the
// restored character. Most keyboards accept finishComposingText and update their internal
// states. One exception is the recent version of Samsung keyboard which works goofily only
// when we finish composing text here. Since it is more difficult to blacklist all Japanese
// keyboards, instead we call finishComposingText() for all the keyboards except for Samsung
// keyboard.
String pkgName = mDelegate.getKeyboardPackageName();
return !pkgName.contains("com.sec.android.inputmethod");
}
@VisibleForTesting
public static boolean isNonCompositionalText(String text) {
// To start with, we are only activating this for English alphabets, European characters,
// numbers and URLs to avoid potential bad interactions with more complex IMEs.
// The rationale for including character sets with diacritical marks is that backspacing on
// a letter with a diacritical mark most likely deletes the whole character instead of
// removing the diacritical mark.
// TODO(changwan): also scan for other traditionally non-IME charsets.
return NON_COMPOSITIONAL_TEXT_PATTERN.matcher(text).matches();
}
@Override
public boolean hasAutocomplete() {
boolean retVal = mCurrentState.hasAutocompleteText();
if (DEBUG) Log.i(TAG, "hasAutocomplete: " + retVal);
return retVal;
}
@Override
public InputConnection getInputConnection() {
return mInputConnection;
}
private void updateSelectionForTesting() {
int selStart = mDelegate.getSelectionStart();
int selEnd = mDelegate.getSelectionEnd();
if (selStart == mLastUpdateSelStart && selEnd == mLastUpdateSelEnd) return;
mLastUpdateSelStart = selStart;
mLastUpdateSelEnd = selEnd;
mDelegate.onUpdateSelectionForTesting(selStart, selEnd);
}
@Override
public boolean shouldIgnoreAccessibilityEvent() {
return mBatchEditNestCount > 0;
}
/**
* A class to set and remove, or do other operations on Span and SpannableString of autocomplete
* text that will be appended to the user text. In addition, cursor will be hidden whenever we
* are showing span to the user.
*/
private static class SpanCursorController {
private final Delegate mDelegate;
private BackgroundColorSpan mSpan;
public SpanCursorController(Delegate delegate) {
mDelegate = delegate;
}
public void setSpan(AutocompleteState state) {
int sel = state.getSelStart();
if (mSpan == null) mSpan = new BackgroundColorSpan(mDelegate.getHighlightColor());
SpannableString spanString = new SpannableString(state.getAutocompleteText());
// The flag here helps make sure that span does not get spill to other part of the text.
spanString.setSpan(mSpan, 0, state.getAutocompleteText().length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Editable editable = mDelegate.getEditableText();
editable.append(spanString);
// Keep the original selection before adding spannable string.
Selection.setSelection(editable, sel, sel);
setCursorVisible(false);
if (DEBUG) Log.i(TAG, "setSpan: " + getEditableDebugString(editable));
}
private void setCursorVisible(boolean visible) {
if (mDelegate.isFocused()) mDelegate.setCursorVisible(visible);
}
private int getSpanIndex(Editable editable) {
if (editable == null || mSpan == null) return -1;
return editable.getSpanStart(mSpan); // returns -1 if mSpan is not attached
}
public void reset() {
setCursorVisible(true);
Editable editable = mDelegate.getEditableText();
int idx = getSpanIndex(editable);
if (idx != -1) {
editable.removeSpan(mSpan);
}
mSpan = null;
}
public boolean removeSpan() {
setCursorVisible(true);
Editable editable = mDelegate.getEditableText();
int idx = getSpanIndex(editable);
if (idx == -1) return false;
if (DEBUG) Log.i(TAG, "removeSpan IDX[%d]", idx);
editable.removeSpan(mSpan);
editable.delete(idx, editable.length());
mSpan = null;
if (DEBUG) {
Log.i(TAG, "removeSpan - after removal: " + getEditableDebugString(editable));
}
return true;
}
public void commitSpan() {
mDelegate.getEditableText().removeSpan(mSpan);
setCursorVisible(true);
}
public void reflectTextUpdateInState(AutocompleteState state, CharSequence text) {
if (text instanceof Editable) {
Editable editable = (Editable) text;
int idx = getSpanIndex(editable);
if (idx != -1) {
// We do not set autocomplete text here as model should solely control it.
state.setUserText(editable.subSequence(0, idx).toString());
return;
}
}
state.setUserText(text.toString());
}
}
private class AutocompleteInputConnection extends InputConnectionWrapper {
private final AutocompleteState mPreBatchEditState;
public AutocompleteInputConnection() {
super(null, true);
mPreBatchEditState = new AutocompleteState(mCurrentState);
}
private boolean incrementBatchEditCount() {
++mBatchEditNestCount;
// After the outermost super.beginBatchEdit(), EditText will stop selection change
// update to the IME app.
return super.beginBatchEdit();
}
private boolean decrementBatchEditCount() {
--mBatchEditNestCount;
boolean retVal = super.endBatchEdit();
if (mBatchEditNestCount == 0) {
// At the outermost super.endBatchEdit(), EditText will resume selection change
// update to the IME app.
updateSelectionForTesting();
}
return retVal;
}
public void commitAutocomplete() {
if (DEBUG) Log.i(TAG, "commitAutocomplete");
if (!hasAutocomplete()) return;
String autocompleteText = mCurrentState.getAutocompleteText();
mCurrentState.commitAutocompleteText();
// Invalidate mPreviouslySetState.
mPreviouslySetState.copyFrom(mCurrentState);
mLastEditWasTyping = false;
if (mBatchEditNestCount == 0) {
incrementBatchEditCount(); // avoids additional notifyAutocompleteTextStateChanged()
mSpanCursorController.commitSpan();
decrementBatchEditCount();
} else {
// We have already removed span in the onBeginImeCommand(), just append the text.
mDelegate.append(autocompleteText);
}
}
@Override
public boolean beginBatchEdit() {
if (DEBUG) Log.i(TAG, "beginBatchEdit");
onBeginImeCommand();
boolean retVal = incrementBatchEditCount();
onEndImeCommand();
return retVal;
}
/**
* Always call this at the beginning of any IME command. Compare this with beginBatchEdit()
* which is by itself an IME command.
* @return Whether the call was successful.
*/
public boolean onBeginImeCommand() {
if (DEBUG) Log.i(TAG, "onBeginImeCommand: " + mBatchEditNestCount);
boolean retVal = incrementBatchEditCount();
if (mBatchEditNestCount == 1) {
mPreBatchEditState.copyFrom(mCurrentState);
} else if (mDeletePostfixOnNextBeginImeCommand > 0) {
int len = mDelegate.getText().length();
mDelegate.getText().delete(len - mDeletePostfixOnNextBeginImeCommand, len);
}
mDeletePostfixOnNextBeginImeCommand = 0;
mSpanCursorController.removeSpan();
return retVal;
}
private void restoreBackspacedText(String diff) {
if (DEBUG) Log.i(TAG, "restoreBackspacedText. diff: " + diff);
if (mBatchEditNestCount > 0) {
// If batch edit hasn't finished, we will restore backspaced text only for visual
// effects. However, for internal operations to work correctly, we need to remove
// the restored diff at the beginning of next IME operation.
mDeletePostfixOnNextBeginImeCommand = diff.length();
}
if (mBatchEditNestCount == 0) { // only at the outermost batch edit
if (shouldFinishCompositionOnDeletion()) super.finishComposingText();
}
incrementBatchEditCount(); // avoids additional notifyAutocompleteTextStateChanged()
Editable editable = mDelegate.getEditableText();
editable.append(diff);
decrementBatchEditCount();
}
private boolean setAutocompleteSpan() {
mSpanCursorController.removeSpan();
if (DEBUG) {
Log.i(TAG, "setAutocompleteSpan. %s->%s", mPreviouslySetState, mCurrentState);
}
if (!mCurrentState.isCursorAtEndOfUserText()) return false;
if (mCurrentState.reuseAutocompleteTextIfPrefixExtension(mPreviouslySetState)) {
mSpanCursorController.setSpan(mCurrentState);
return true;
} else {
return false;
}
}
@Override
public boolean endBatchEdit() {
if (DEBUG) Log.i(TAG, "endBatchEdit");
onBeginImeCommand();
boolean retVal = decrementBatchEditCount();
onEndImeCommand();
return retVal;
}
/**
* Always call this at the end of an IME command. Compare this with endBatchEdit()
* which is by itself an IME command.
* @return Whether the call was successful.
*/
public boolean onEndImeCommand() {
if (DEBUG) Log.i(TAG, "onEndImeCommand: " + (mBatchEditNestCount - 1));
String diff = mCurrentState.getBackwardDeletedTextFrom(mPreBatchEditState);
if (diff != null) {
// Update selection first such that keyboard app gets what it expects.
boolean retVal = decrementBatchEditCount();
if (mPreBatchEditState.hasAutocompleteText()) {
// Undo delete to retain the last character and only remove autocomplete text.
restoreBackspacedText(diff);
}
mLastEditWasTyping = false;
clearAutocompleteText();
notifyAutocompleteTextStateChanged();
return retVal;
}
if (!setAutocompleteSpan()) {
clearAutocompleteText();
}
boolean retVal = decrementBatchEditCount();
// Simply typed some characters or whole text selection has been overridden.
if (mCurrentState.isForwardTypedFrom(mPreBatchEditState)
|| (mPreBatchEditState.isWholeUserTextSelected()
&& mCurrentState.getUserText().length() > 0
&& mCurrentState.isCursorAtEndOfUserText())) {
mLastEditWasTyping = true;
}
notifyAutocompleteTextStateChanged();
return retVal;
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
if (DEBUG) Log.i(TAG, "commitText: " + text);
onBeginImeCommand();
boolean retVal = super.commitText(text, newCursorPosition);
onEndImeCommand();
return retVal;
}
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
if (DEBUG) Log.i(TAG, "setComposingText: " + text);
onBeginImeCommand();
boolean retVal = super.setComposingText(text, newCursorPosition);
onEndImeCommand();
return retVal;
}
@Override
public boolean setComposingRegion(int start, int end) {
if (DEBUG) Log.i(TAG, "setComposingRegion: [%d,%d]", start, end);
onBeginImeCommand();
boolean retVal = super.setComposingRegion(start, end);
onEndImeCommand();
return retVal;
}
@Override
public boolean finishComposingText() {
if (DEBUG) Log.i(TAG, "finishComposingText");
onBeginImeCommand();
boolean retVal = super.finishComposingText();
onEndImeCommand();
return retVal;
}
@Override
public boolean deleteSurroundingText(final int beforeLength, final int afterLength) {
if (DEBUG) Log.i(TAG, "deleteSurroundingText [%d,%d]", beforeLength, afterLength);
onBeginImeCommand();
boolean retVal = super.deleteSurroundingText(beforeLength, afterLength);
onEndImeCommand();
return retVal;
}
@Override
public boolean setSelection(final int start, final int end) {
if (DEBUG) Log.i(TAG, "setSelection [%d,%d]", start, end);
onBeginImeCommand();
boolean retVal = super.setSelection(start, end);
onEndImeCommand();
return retVal;
}
@Override
public boolean performEditorAction(final int editorAction) {
if (DEBUG) Log.i(TAG, "performEditorAction: " + editorAction);
onBeginImeCommand();
commitAutocomplete();
boolean retVal = super.performEditorAction(editorAction);
onEndImeCommand();
return retVal;
}
@Override
public boolean sendKeyEvent(final KeyEvent event) {
if (DEBUG) Log.i(TAG, "sendKeyEvent: " + event.getKeyCode());
onBeginImeCommand();
boolean retVal = super.sendKeyEvent(event);
onEndImeCommand();
return retVal;
}
@Override
public ExtractedText getExtractedText(final ExtractedTextRequest request, final int flags) {
if (DEBUG) Log.i(TAG, "getExtractedText");
onBeginImeCommand();
ExtractedText retVal = super.getExtractedText(request, flags);
onEndImeCommand();
return retVal;
}
@Override
public CharSequence getTextAfterCursor(final int n, final int flags) {
if (DEBUG) Log.i(TAG, "getTextAfterCursor");
onBeginImeCommand();
CharSequence retVal = super.getTextAfterCursor(n, flags);
onEndImeCommand();
return retVal;
}
@Override
public CharSequence getTextBeforeCursor(final int n, final int flags) {
if (DEBUG) Log.i(TAG, "getTextBeforeCursor");
onBeginImeCommand();
CharSequence retVal = super.getTextBeforeCursor(n, flags);
onEndImeCommand();
return retVal;
}
@Override
public CharSequence getSelectedText(final int flags) {
if (DEBUG) Log.i(TAG, "getSelectedText");
onBeginImeCommand();
CharSequence retVal = super.getSelectedText(flags);
onEndImeCommand();
return retVal;
}
@Override
public boolean commitCompletion(CompletionInfo text) {
if (DEBUG) Log.i(TAG, "commitCompletion");
onBeginImeCommand();
boolean retVal = super.commitCompletion(text);
onEndImeCommand();
return retVal;
}
@Override
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
if (DEBUG) Log.i(TAG, "commitContent");
onBeginImeCommand();
boolean retVal = super.commitContent(inputContentInfo, flags, opts);
onEndImeCommand();
return retVal;
}
@Override
public boolean commitCorrection(CorrectionInfo correctionInfo) {
if (DEBUG) Log.i(TAG, "commitCorrection");
onBeginImeCommand();
boolean retVal = super.commitCorrection(correctionInfo);
onEndImeCommand();
return retVal;
}
@Override
public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
if (DEBUG) Log.i(TAG, "deleteSurroundingTextInCodePoints");
onBeginImeCommand();
boolean retVal = super.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
onEndImeCommand();
return retVal;
}
@Override
public int getCursorCapsMode(int reqModes) {
if (DEBUG) Log.i(TAG, "getCursorCapsMode");
onBeginImeCommand();
int retVal = super.getCursorCapsMode(reqModes);
onEndImeCommand();
return retVal;
}
@Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
if (DEBUG) Log.i(TAG, "requestCursorUpdates");
onBeginImeCommand();
boolean retVal = super.requestCursorUpdates(cursorUpdateMode);
onEndImeCommand();
return retVal;
}
@Override
public boolean clearMetaKeyStates(int states) {
if (DEBUG) Log.i(TAG, "clearMetaKeyStates");
onBeginImeCommand();
boolean retVal = super.clearMetaKeyStates(states);
onEndImeCommand();
return retVal;
}
}
}