Newer
Older
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.util.Log;
import android.view.KeyEvent;
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.InputMethodManager;
import org.futo.inputmethod.compat.InputConnectionCompatUtils;
import org.futo.inputmethod.latin.common.Constants;
import org.futo.inputmethod.latin.common.UnicodeSurrogate;
import org.futo.inputmethod.latin.common.StringUtils;
import org.futo.inputmethod.latin.inputlogic.PrivateCommandPerformer;
import org.futo.inputmethod.latin.settings.SpacingAndPunctuations;
import org.futo.inputmethod.latin.utils.CapsModeUtils;
import org.futo.inputmethod.latin.utils.DebugLogUtils;
import org.futo.inputmethod.latin.utils.NgramContextUtils;
import org.futo.inputmethod.latin.utils.ScriptUtils;
import org.futo.inputmethod.latin.utils.SpannableStringUtils;
import org.futo.inputmethod.latin.utils.StatsUtils;
import org.futo.inputmethod.latin.utils.TextRange;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
* Enrichment class for InputConnection to simplify interaction and add functionality.
*
* This class serves as a wrapper to be able to simply add hooks to any calls to the underlying
* InputConnection. It also keeps track of a number of things to avoid having to call upon IPC
* all the time to find out what text is in the buffer, when we need it to determine caps mode
* for example.
public final class RichInputConnection implements PrivateCommandPerformer {
private static final String TAG = "RichInputConnection";
private static final boolean DBG = false;
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
private static final int NUM_CHARS_TO_GET_BEFORE_CURSOR = 40;
private static final int NUM_CHARS_TO_GET_AFTER_CURSOR = 40;
private static final int INVALID_CURSOR_POSITION = -1;
* The amount of time a {@link #reloadTextCache} call needs to take for the keyboard to enter
* the {@link #hasSlowInputConnection} state.
private static final long SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS = 1000;
/**
* The amount of time a {@link #getTextBeforeCursor} or {@link #getTextAfterCursor} call needs
* to take for the keyboard to enter the {@link #hasSlowInputConnection} state.
*/
private static final long SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS = 200;
private static final int OPERATION_GET_TEXT_BEFORE_CURSOR = 0;
private static final int OPERATION_GET_TEXT_AFTER_CURSOR = 1;
private static final int OPERATION_GET_WORD_RANGE_AT_CURSOR = 2;
private static final int OPERATION_RELOAD_TEXT_CACHE = 3;
private static final String[] OPERATION_NAMES = new String[] {
"GET_TEXT_BEFORE_CURSOR",
"GET_TEXT_AFTER_CURSOR",
"GET_WORD_RANGE_AT_CURSOR",
"RELOAD_TEXT_CACHE"};
* The amount of time the keyboard will persist in the {@link #hasSlowInputConnection} state
* after observing a slow InputConnection event.
*/
private static final long SLOW_INPUTCONNECTION_PERSIST_MS = TimeUnit.MINUTES.toMillis(10);
* This variable contains an expected value for the selection start position. This is where the
* cursor or selection start may end up after all the keyboard-triggered updates have passed. We
* keep this to compare it to the actual selection start to guess whether the move was caused by
* a keyboard command or not.
* It's not really the selection start position: the selection start may not be there yet, and
* in some cases, it may never arrive there.
private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is
* expected. The same caveats as mExpectedSelStart apply.
*/
private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* This contains the committed text immediately preceding the cursor and the composing
* text, if any. It is refreshed when the cursor moves by calling upon the TextView.
private final StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
/**
* This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
*/
private final StringBuilder mComposingText = new StringBuilder();
* This variable is a temporary object used in {@link #commitText(CharSequence,int)}
* to avoid object creation.
*/
private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder();
private final InputMethodService mParent;
private InputConnection mIC;
private int mNestLevel;
/**
* The timestamp of the last slow InputConnection operation
*/
private long mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS;
public RichInputConnection(final InputMethodService parent) {
Aleksandras Kostarevas
committed
if(parent == null) throw new NullPointerException("Input Method Service is null");
mIC = null;
mNestLevel = 0;
}
public boolean isConnected() {
return mIC != null;
}
/**
* Returns whether or not the underlying InputConnection is slow. When true, we want to avoid
* calling InputConnection methods that trigger an IPC round-trip (e.g., getTextAfterCursor).
*/
public boolean hasSlowInputConnection() {
return (SystemClock.uptimeMillis() - mLastSlowInputConnectionTime)
<= SLOW_INPUTCONNECTION_PERSIST_MS;
}
public void onStartInput() {
mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS;
private void checkConsistencyForDebug() {
final ExtractedTextRequest r = new ExtractedTextRequest();
r.hintMaxChars = 0;
r.hintMaxLines = 0;
r.token = 1;
r.flags = 0;
final ExtractedText et = mIC.getExtractedText(r, 0);
final CharSequence beforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
0);
final StringBuilder internal = new StringBuilder(mCommittedTextBeforeComposingText)
.append(mComposingText);
if (null == et || null == beforeCursor) return;
final int actualLength = Math.min(beforeCursor.length(), internal.length());
if (internal.length() > actualLength) {
internal.delete(0, internal.length() - actualLength);
}
final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
: beforeCursor.subSequence(beforeCursor.length() - actualLength,
beforeCursor.length()).toString();
if (et.selectionStart != mExpectedSelStart
|| !(reference.equals(internal.toString()))) {
final String context = "Expected selection start = " + mExpectedSelStart
+ "\nActual selection start = " + et.selectionStart
+ "\nExpected text = " + internal.length() + " " + internal
+ "\nActual text = " + reference.length() + " " + reference;
Aleksandras Kostarevas
committed
throw new RuntimeException(context);
Log.e(TAG, DebugLogUtils.getStackTrace(2));
Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart);
public void beginBatchEdit() {
mIC = mParent.getCurrentInputConnection();
Loading
Loading full blame...