15475b38328171a0841ae18074bd45380ec567e90Jean Chalard/* 25475b38328171a0841ae18074bd45380ec567e90Jean Chalard * Copyright (C) 2012 The Android Open Source Project 35475b38328171a0841ae18074bd45380ec567e90Jean Chalard * 48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); 58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License. 68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at 75475b38328171a0841ae18074bd45380ec567e90Jean Chalard * 88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0 95475b38328171a0841ae18074bd45380ec567e90Jean Chalard * 105475b38328171a0841ae18074bd45380ec567e90Jean Chalard * Unless required by applicable law or agreed to in writing, software 118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, 128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and 148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License. 155475b38328171a0841ae18074bd45380ec567e90Jean Chalard */ 165475b38328171a0841ae18074bd45380ec567e90Jean Chalard 175475b38328171a0841ae18074bd45380ec567e90Jean Chalardpackage com.android.inputmethod.latin; 185475b38328171a0841ae18074bd45380ec567e90Jean Chalard 19f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalardimport android.inputmethodservice.InputMethodService; 209273f3832b51f5d23d86df624600381ed6d6585fJean Chalardimport android.os.Build; 21c92c883fdf2287b49392692fa2e8d109dc26f785David Fadenimport android.os.Bundle; 2273aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovicimport android.os.SystemClock; 234a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawaimport android.text.SpannableStringBuilder; 24bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalardimport android.text.TextUtils; 25f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovicimport android.text.style.CharacterStyle; 265475b38328171a0841ae18074bd45380ec567e90Jean Chalardimport android.util.Log; 275475b38328171a0841ae18074bd45380ec567e90Jean Chalardimport android.view.KeyEvent; 285475b38328171a0841ae18074bd45380ec567e90Jean Chalardimport android.view.inputmethod.CompletionInfo; 295475b38328171a0841ae18074bd45380ec567e90Jean Chalardimport android.view.inputmethod.CorrectionInfo; 3002308bec632a5df23325c916bffec5def16b22b4Jean Chalardimport android.view.inputmethod.ExtractedText; 3102308bec632a5df23325c916bffec5def16b22b4Jean Chalardimport android.view.inputmethod.ExtractedTextRequest; 325475b38328171a0841ae18074bd45380ec567e90Jean Chalardimport android.view.inputmethod.InputConnection; 3329200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawaimport android.view.inputmethod.InputMethodManager; 345475b38328171a0841ae18074bd45380ec567e90Jean Chalard 3529200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawaimport com.android.inputmethod.compat.InputConnectionCompatUtils; 369342484e8d573a40f470b6a593df31c602fa4076Ken Wakasaimport com.android.inputmethod.latin.common.Constants; 37f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovicimport com.android.inputmethod.latin.common.UnicodeSurrogate; 384beeb9253a06482299e0c67467531d30436a02fcJean Chalardimport com.android.inputmethod.latin.common.StringUtils; 39c92c883fdf2287b49392692fa2e8d109dc26f785David Fadenimport com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer; 40494e2d6c17cdbf27615a2fbc02b12d2562bf7cd3Tadashi G. Takaokaimport com.android.inputmethod.latin.settings.SpacingAndPunctuations; 41e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.CapsModeUtils; 4203118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasaimport com.android.inputmethod.latin.utils.DebugLogUtils; 43bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagiimport com.android.inputmethod.latin.utils.NgramContextUtils; 44292deb632cbab232334190e68d29184094d6d51bJean Chalardimport com.android.inputmethod.latin.utils.ScriptUtils; 45f56b82f80961a511765df206eee36229cbee6ed8Ken Wakasaimport com.android.inputmethod.latin.utils.SpannableStringUtils; 4673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovicimport com.android.inputmethod.latin.utils.StatsUtils; 47675bcf191c3cdb5ba5af70efb9357ffceb389c2eJean Chalardimport com.android.inputmethod.latin.utils.TextRange; 48bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard 49912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyangimport java.util.concurrent.TimeUnit; 50912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang 51d3a4c5132422b189c8dbb94dbbe84a9b9761b0a8Tadashi G. Takaokaimport javax.annotation.Nonnull; 52db96122787a57632136566e2448c21aa96879955Mohammadinamul Sheikimport javax.annotation.Nullable; 53d3a4c5132422b189c8dbb94dbbe84a9b9761b0a8Tadashi G. Takaoka 545475b38328171a0841ae18074bd45380ec567e90Jean Chalard/** 5528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * Enrichment class for InputConnection to simplify interaction and add functionality. 5628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * 5728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * This class serves as a wrapper to be able to simply add hooks to any calls to the underlying 5828d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * InputConnection. It also keeps track of a number of things to avoid having to call upon IPC 5928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * all the time to find out what text is in the buffer, when we need it to determine caps mode 6028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * for example. 615475b38328171a0841ae18074bd45380ec567e90Jean Chalard */ 62c92c883fdf2287b49392692fa2e8d109dc26f785David Fadenpublic final class RichInputConnection implements PrivateCommandPerformer { 630232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic private static final String TAG = "RichInputConnection"; 645475b38328171a0841ae18074bd45380ec567e90Jean Chalard private static final boolean DBG = false; 6528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard private static final boolean DEBUG_PREVIOUS_TEXT = false; 66574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard private static final boolean DEBUG_BATCH_NESTING = false; 670232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic private static final int NUM_CHARS_TO_GET_BEFORE_CURSOR = 40; 680232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic private static final int NUM_CHARS_TO_GET_AFTER_CURSOR = 40; 6902308bec632a5df23325c916bffec5def16b22b4Jean Chalard private static final int INVALID_CURSOR_POSITION = -1; 70912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang 71912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang /** 72563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic * The amount of time a {@link #reloadTextCache} call needs to take for the keyboard to enter 73563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic * the {@link #hasSlowInputConnection} state. 74912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang */ 75563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic private static final long SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS = 1000; 76563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic /** 77563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic * The amount of time a {@link #getTextBeforeCursor} or {@link #getTextAfterCursor} call needs 78563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic * to take for the keyboard to enter the {@link #hasSlowInputConnection} state. 79563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic */ 80563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic private static final long SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS = 200; 81563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic 8273aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private static final int OPERATION_GET_TEXT_BEFORE_CURSOR = 0; 8373aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private static final int OPERATION_GET_TEXT_AFTER_CURSOR = 1; 8473aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private static final int OPERATION_GET_WORD_RANGE_AT_CURSOR = 2; 8573aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private static final int OPERATION_RELOAD_TEXT_CACHE = 3; 8673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private static final String[] OPERATION_NAMES = new String[] { 8773aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic "GET_TEXT_BEFORE_CURSOR", 8873aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic "GET_TEXT_AFTER_CURSOR", 8973aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic "GET_WORD_RANGE_AT_CURSOR", 9073aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic "RELOAD_TEXT_CACHE"}; 9102308bec632a5df23325c916bffec5def16b22b4Jean Chalard 9228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard /** 93563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic * The amount of time the keyboard will persist in the {@link #hasSlowInputConnection} state 94912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang * after observing a slow InputConnection event. 95912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang */ 96912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang private static final long SLOW_INPUTCONNECTION_PERSIST_MS = TimeUnit.MINUTES.toMillis(10); 97912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang 98912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang /** 992fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * This variable contains an expected value for the selection start position. This is where the 1002fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * cursor or selection start may end up after all the keyboard-triggered updates have passed. We 1012fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * keep this to compare it to the actual selection start to guess whether the move was caused by 1022fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * a keyboard command or not. 1032fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * It's not really the selection start position: the selection start may not be there yet, and 1042fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * in some cases, it may never arrive there. 10528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard */ 1062fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points 1072fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa /** 1082fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is 1092fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * expected. The same caveats as mExpectedSelStart apply. 1102fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa */ 1112fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points 11228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard /** 11328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * This contains the committed text immediately preceding the cursor and the composing 1140232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic * text, if any. It is refreshed when the cursor moves by calling upon the TextView. 11528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard */ 116740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard private final StringBuilder mCommittedTextBeforeComposingText = new StringBuilder(); 11728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard /** 11828d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard * This contains the currently composing text, as LatinIME thinks the TextView is seeing it. 11928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard */ 120740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard private final StringBuilder mComposingText = new StringBuilder(); 12128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard 1224a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa /** 123c71e4d65343bf782d80ce508786befa6ee3261cfDan Zivkovic * This variable is a temporary object used in {@link #commitText(CharSequence,int)} 124c71e4d65343bf782d80ce508786befa6ee3261cfDan Zivkovic * to avoid object creation. 1254a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa */ 1264a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder(); 1274a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa 128f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard private final InputMethodService mParent; 129563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic private InputConnection mIC; 130563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic private int mNestLevel; 131edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic 132912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang /** 133912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang * The timestamp of the last slow InputConnection operation 134912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang */ 13596640a110b97bbb48bc45ef4671d3927952e6cbaTom Ouyang private long mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS; 136912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang 137f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard public RichInputConnection(final InputMethodService parent) { 138f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mParent = parent; 1395475b38328171a0841ae18074bd45380ec567e90Jean Chalard mIC = null; 1405475b38328171a0841ae18074bd45380ec567e90Jean Chalard mNestLevel = 0; 1415475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 1425475b38328171a0841ae18074bd45380ec567e90Jean Chalard 143edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic public boolean isConnected() { 144edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic return mIC != null; 145edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic } 146edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic 147912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang /** 148912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang * Returns whether or not the underlying InputConnection is slow. When true, we want to avoid 149912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang * calling InputConnection methods that trigger an IPC round-trip (e.g., getTextAfterCursor). 150912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang */ 151912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang public boolean hasSlowInputConnection() { 15296640a110b97bbb48bc45ef4671d3927952e6cbaTom Ouyang return (SystemClock.uptimeMillis() - mLastSlowInputConnectionTime) 153912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang <= SLOW_INPUTCONNECTION_PERSIST_MS; 154912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang } 155912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang 156912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang public void onStartInput() { 15796640a110b97bbb48bc45ef4671d3927952e6cbaTom Ouyang mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS; 158912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang } 159912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang 16028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard private void checkConsistencyForDebug() { 16128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final ExtractedTextRequest r = new ExtractedTextRequest(); 16228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard r.hintMaxChars = 0; 16328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard r.hintMaxLines = 0; 16428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard r.token = 1; 16528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard r.flags = 0; 16628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final ExtractedText et = mIC.getExtractedText(r, 0); 167f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard final CharSequence beforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 168f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard 0); 1696a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka final StringBuilder internal = new StringBuilder(mCommittedTextBeforeComposingText) 17028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard .append(mComposingText); 17128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (null == et || null == beforeCursor) return; 17228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final int actualLength = Math.min(beforeCursor.length(), internal.length()); 17328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (internal.length() > actualLength) { 17428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard internal.delete(0, internal.length() - actualLength); 17528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 17628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString() 17728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard : beforeCursor.subSequence(beforeCursor.length() - actualLength, 17828d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard beforeCursor.length()).toString(); 1792fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (et.selectionStart != mExpectedSelStart 18028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard || !(reference.equals(internal.toString()))) { 1812fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final String context = "Expected selection start = " + mExpectedSelStart 1822fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa + "\nActual selection start = " + et.selectionStart 18328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard + "\nExpected text = " + internal.length() + " " + internal 18428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard + "\nActual text = " + reference.length() + " " + reference; 18528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); 18628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } else { 18703118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa Log.e(TAG, DebugLogUtils.getStackTrace(2)); 1882fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart); 18928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 19028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 19128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard 192f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard public void beginBatchEdit() { 1935475b38328171a0841ae18074bd45380ec567e90Jean Chalard if (++mNestLevel == 1) { 194f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 195edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 19694e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.beginBatchEdit(); 19794e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 1985475b38328171a0841ae18074bd45380ec567e90Jean Chalard } else { 1995475b38328171a0841ae18074bd45380ec567e90Jean Chalard if (DBG) { 2005475b38328171a0841ae18074bd45380ec567e90Jean Chalard throw new RuntimeException("Nest level too deep"); 2015475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 2025f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka Log.e(TAG, "Nest level too deep : " + mNestLevel); 2035475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 204574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 20528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 2065475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 20728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard 2085475b38328171a0841ae18074bd45380ec567e90Jean Chalard public void endBatchEdit() { 2095475b38328171a0841ae18074bd45380ec567e90Jean Chalard if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead 210edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (--mNestLevel == 0 && isConnected()) { 21194e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.endBatchEdit(); 21294e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 21328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 21428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 21528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard 216f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard /** 217f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * Reset the cached text and retrieve it again from the editor. 218f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * 219f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * This should be called when the cursor moved. It's possible that we can't connect to 220f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * the application when doing this; notably, this happens sometimes during rotation, probably 221f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * because of a race condition in the framework. In this case, we just can't retrieve the 222f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * data, so we empty the cache and note that we don't know the new cursor position, and we 223f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * return false so that the caller knows about this and can retry later. 224f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * 2252fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param newSelStart the new position of the selection start, as received from the system. 2262fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param newSelEnd the new position of the selection end, as received from the system. 2272fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param shouldFinishComposition whether we should finish the composition in progress. 228f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * @return true if we were able to connect to the editor successfully, false otherwise. When 229f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * this method returns false, the caches could not be correctly refreshed so they were only 230f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard * reset: the caller should try again later to return to normal operation. 231f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard */ 2322fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newSelStart, 2332fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final int newSelEnd, final boolean shouldFinishComposition) { 2342fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart = newSelStart; 2352fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = newSelEnd; 23628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(0); 2372fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final boolean didReloadTextSuccessfully = reloadTextCache(); 2382fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (!didReloadTextSuccessfully) { 2392fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa Log.d(TAG, "Will try to retrieve text later."); 2402fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return false; 2412fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa } 242edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected() && shouldFinishComposition) { 2432fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mIC.finishComposingText(); 2442fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa } 2452fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return true; 2462fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa } 2472fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa 2482fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa /** 2492fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * Reload the cached text from the InputConnection. 2502fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * 2512fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @return true if successful 2522fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa */ 2532fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa private boolean reloadTextCache() { 25428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mCommittedTextBeforeComposingText.setLength(0); 255f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard mIC = mParent.getCurrentInputConnection(); 256f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard // Call upon the inputconnection directly since our own method is using the cache, and 257f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard // we want to refresh it. 25873aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final CharSequence textBeforeCursor = getTextBeforeCursorAndDetectLaggyConnection( 25973aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic OPERATION_RELOAD_TEXT_CACHE, 260563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS, 26173aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic Constants.EDITOR_CONTENTS_CACHE_SIZE, 26273aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic 0 /* flags */); 263f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard if (null == textBeforeCursor) { 264f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard // For some reason the app thinks we are not connected to it. This looks like a 265f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard // framework bug... Fall back to ground state and return false. 2662fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart = INVALID_CURSOR_POSITION; 2672fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = INVALID_CURSOR_POSITION; 2682fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa Log.e(TAG, "Unable to connect to the editor to retrieve text."); 269f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard return false; 270f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard } 271f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard mCommittedTextBeforeComposingText.append(textBeforeCursor); 272f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard return true; 2735475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 2745475b38328171a0841ae18074bd45380ec567e90Jean Chalard 2759d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard private void checkBatchEdit() { 2769d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard if (mNestLevel != 1) { 2779d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard // TODO: exception instead 2789d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard Log.e(TAG, "Batch edit level incorrect : " + mNestLevel); 27903118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa Log.e(TAG, DebugLogUtils.getStackTrace(4)); 2809d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard } 2819d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard } 2829d71748ba48dbc8793f3e1ecddf5fd31b8e59613Jean Chalard 2835475b38328171a0841ae18074bd45380ec567e90Jean Chalard public void finishComposingText() { 284574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 28528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 286fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // TODO: this is not correct! The cursor is not necessarily after the composing text. 287fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // In the practice right now this is only called when input ends so it will be reset so 288fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // it works, but it's wrong and should be fixed. 28928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mCommittedTextBeforeComposingText.append(mComposingText); 29028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(0); 291edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 29294e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.finishComposingText(); 29394e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 2945475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 2955475b38328171a0841ae18074bd45380ec567e90Jean Chalard 2964a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa /** 297c71e4d65343bf782d80ce508786befa6ee3261cfDan Zivkovic * Calls {@link InputConnection#commitText(CharSequence, int)}. 298c71e4d65343bf782d80ce508786befa6ee3261cfDan Zivkovic * 2994a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa * @param text The text to commit. This may include styles. 3004a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa * @param newCursorPosition The new cursor position around the text. 3014a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa */ 3024a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa public void commitText(final CharSequence text, final int newCursorPosition) { 303574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 30428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 30528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mCommittedTextBeforeComposingText.append(text); 306fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // TODO: the following is exceedingly error-prone. Right now when the cursor is in the 307fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // middle of the composing word mComposingText only holds the part of the composing text 308fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // that is before the cursor, so this actually works, but it's terribly confusing. Fix this. 3092fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart += text.length() - mComposingText.length(); 3102fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 31128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(0); 312edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 313f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic mTempObjectForCommitText.clear(); 314f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic mTempObjectForCommitText.append(text); 315f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic final CharacterStyle[] spans = mTempObjectForCommitText.getSpans( 316f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic 0, text.length(), CharacterStyle.class); 317f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic for (final CharacterStyle span : spans) { 318f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic final int spanStart = mTempObjectForCommitText.getSpanStart(span); 319f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic final int spanEnd = mTempObjectForCommitText.getSpanEnd(span); 320f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic final int spanFlags = mTempObjectForCommitText.getSpanFlags(span); 321f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic // We have to adjust the end of the span to include an additional character. 322f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic // This is to avoid splitting a unicode surrogate pair. 323f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic // See com.android.inputmethod.latin.common.Constants.UnicodeSurrogate 324f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic // See https://b.corp.google.com/issues/19255233 325f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic if (0 < spanEnd && spanEnd < mTempObjectForCommitText.length()) { 326f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic final char spanEndChar = mTempObjectForCommitText.charAt(spanEnd - 1); 327f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic final char nextChar = mTempObjectForCommitText.charAt(spanEnd); 328f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic if (UnicodeSurrogate.isLowSurrogate(spanEndChar) 329f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic && UnicodeSurrogate.isHighSurrogate(nextChar)) { 330f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic mTempObjectForCommitText.setSpan(span, spanStart, spanEnd + 1, spanFlags); 331f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic } 332f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic } 333f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic } 334f3c319fb8ac29448c491af95261a4ce01b64a59cDan Zivkovic mIC.commitText(mTempObjectForCommitText, newCursorPosition); 3354a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa } 3364a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa } 3374a71d2c48021351e10bd61d5ee007533651da627Yohei Yukawa 338db96122787a57632136566e2448c21aa96879955Mohammadinamul Sheik @Nullable 3392995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard public CharSequence getSelectedText(final int flags) { 340edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic return isConnected() ? mIC.getSelectedText(flags) : null; 3412995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard } 3422995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard 343f87e8f7ec1efb93398d909c67468d716b0248fe7Tadashi G. Takaoka public boolean canDeleteCharacters() { 3442fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return mExpectedSelStart > 0; 345f87e8f7ec1efb93398d909c67468d716b0248fe7Tadashi G. Takaoka } 346f87e8f7ec1efb93398d909c67468d716b0248fe7Tadashi G. Takaoka 34790a91272447fd64bc54e06f08f45b11c45930767Jean Chalard /** 34890a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * Gets the caps modes we should be in after this specific string. 34990a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * 35090a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * This returns a bit set of TextUtils#CAP_MODE_*, masked by the inputType argument. 35190a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * This method also supports faking an additional space after the string passed in argument, 35290a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * to support cases where a space will be added automatically, like in phantom space 35390a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * state for example. 35490a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * Note that for English, we are using American typography rules (which are not specific to 35590a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * American English, it's just the most common set of rules for English). 35690a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * 35790a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * @param inputType a mask of the caps modes to test for. 3586a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka * @param spacingAndPunctuations the values of the settings to use for locale and separators. 35990a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * @param hasSpaceBefore if we should consider there should be a space after the string. 36090a91272447fd64bc54e06f08f45b11c45930767Jean Chalard * @return the caps modes that should be on as a set of bits 36190a91272447fd64bc54e06f08f45b11c45930767Jean Chalard */ 3626a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka public int getCursorCapsMode(final int inputType, 3636a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka final SpacingAndPunctuations spacingAndPunctuations, final boolean hasSpaceBefore) { 364f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 365edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (!isConnected()) { 366edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic return Constants.TextUtils.CAP_MODE_OFF; 367edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic } 368d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard if (!TextUtils.isEmpty(mComposingText)) { 369d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard if (hasSpaceBefore) { 370d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard // If we have some composing text and a space before, then we should have 371d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard // MODE_CHARACTERS and MODE_WORDS on. 372d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & inputType; 373d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard } 3745f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka // We have some composing text - we should be in MODE_CHARACTERS only. 3755f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka return TextUtils.CAP_MODE_CHARACTERS & inputType; 376d461bfd6601dfb6d4a4f78be0ff129597af895ffJean Chalard } 3779d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // TODO: this will generally work, but there may be cases where the buffer contains SOME 3789d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // information but not enough to determine the caps mode accurately. This may happen after 3799d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so. 3809d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // getCapsMode should be updated to be able to return a "not enough info" result so that 3819d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // we can get more context only when needed. 3822fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedSelStart) { 3832fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (!reloadTextCache()) { 3842fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa Log.w(TAG, "Unable to connect to the editor. " 3852fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa + "Setting caps mode without knowing text."); 3868f02f1a118d36a1c4143b62f7e20727b2d2bf363Jean Chalard } 3879d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard } 3889d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // This never calls InputConnection#getCapsMode - in fact, it's a static method that 3899d1c73ffd88cd1bfef3de048b0b3a9a7dfbcfa70Jean Chalard // never blocks or initiates IPC. 3904097a20b36c7a6678f2b7486d587b7c43852c2e1Jean Chalard // TODO: don't call #toString() here. Instead, all accesses to 3914097a20b36c7a6678f2b7486d587b7c43852c2e1Jean Chalard // mCommittedTextBeforeComposingText should be done on the main thread. 3924097a20b36c7a6678f2b7486d587b7c43852c2e1Jean Chalard return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText.toString(), inputType, 3936a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka spacingAndPunctuations, hasSpaceBefore); 3945475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 3955475b38328171a0841ae18074bd45380ec567e90Jean Chalard 396e91d495c53a2606962159cfddada2b7a5e206c4cJean Chalard public int getCodePointBeforeCursor() { 3976a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka final int length = mCommittedTextBeforeComposingText.length(); 3986a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka if (length < 1) return Constants.NOT_A_CODE; 3996a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka return Character.codePointBefore(mCommittedTextBeforeComposingText, length); 400e91d495c53a2606962159cfddada2b7a5e206c4cJean Chalard } 401e91d495c53a2606962159cfddada2b7a5e206c4cJean Chalard 4028c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard public CharSequence getTextBeforeCursor(final int n, final int flags) { 4038c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard final int cachedLength = 4048c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard mCommittedTextBeforeComposingText.length() + mComposingText.length(); 4058c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard // If we have enough characters to satisfy the request, or if we have all characters in 4068c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard // the text field, then we can return the cached version right away. 4073a9b2430a56cb2d774070bd8ecd661d0dfb82484Jean Chalard // However, if we don't have an expected cursor position, then we should always 4083a9b2430a56cb2d774070bd8ecd661d0dfb82484Jean Chalard // go fetch the cache again (as it happens, INVALID_CURSOR_POSITION < 0, so we need to 4093a9b2430a56cb2d774070bd8ecd661d0dfb82484Jean Chalard // test for this explicitly) 4102fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (INVALID_CURSOR_POSITION != mExpectedSelStart 4112fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa && (cachedLength >= n || cachedLength >= mExpectedSelStart)) { 4128c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); 4138a1675379e03bd7830d35212fd987346927068a9Jean Chalard // We call #toString() here to create a temporary object. 4148a1675379e03bd7830d35212fd987346927068a9Jean Chalard // In some situations, this method is called on a worker thread, and it's possible 4158a1675379e03bd7830d35212fd987346927068a9Jean Chalard // the main thread touches the contents of mComposingText while this worker thread 4168a1675379e03bd7830d35212fd987346927068a9Jean Chalard // is suspended, because mComposingText is a StringBuilder. This may lead to crashes, 4178a1675379e03bd7830d35212fd987346927068a9Jean Chalard // so we call #toString() on it. That will result in the return value being strictly 4188a1675379e03bd7830d35212fd987346927068a9Jean Chalard // speaking wrong, but since this is used for basing bigram probability off, and 4198a1675379e03bd7830d35212fd987346927068a9Jean Chalard // it's only going to matter for one getSuggestions call, it's fine in the practice. 4208a1675379e03bd7830d35212fd987346927068a9Jean Chalard s.append(mComposingText.toString()); 4218c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard if (s.length() > n) { 4228c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard s.delete(0, s.length() - n); 4238c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard } 4248c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard return s; 4258c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard } 42673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic return getTextBeforeCursorAndDetectLaggyConnection( 427563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic OPERATION_GET_TEXT_BEFORE_CURSOR, 428563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, 429563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic n, flags); 43073aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic } 43173aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic 43273aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private CharSequence getTextBeforeCursorAndDetectLaggyConnection( 433563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic final int operation, final long timeout, final int n, final int flags) { 434f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 43573aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic if (!isConnected()) { 43673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic return null; 43773aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic } 438912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang final long startTime = SystemClock.uptimeMillis(); 43973aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final CharSequence result = mIC.getTextBeforeCursor(n, flags); 440563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic detectLaggyConnection(operation, timeout, startTime); 44173aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic return result; 4425475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 4435475b38328171a0841ae18074bd45380ec567e90Jean Chalard 4448c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard public CharSequence getTextAfterCursor(final int n, final int flags) { 44573aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic return getTextAfterCursorAndDetectLaggyConnection( 446563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic OPERATION_GET_TEXT_AFTER_CURSOR, 447563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, 448563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic n, flags); 44973aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic } 45073aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic 45173aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic private CharSequence getTextAfterCursorAndDetectLaggyConnection( 452563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic final int operation, final long timeout, final int n, final int flags) { 453f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 45473aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic if (!isConnected()) { 45573aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic return null; 45673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic } 45773aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final long startTime = SystemClock.uptimeMillis(); 45873aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final CharSequence result = mIC.getTextAfterCursor(n, flags); 459563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic detectLaggyConnection(operation, timeout, startTime); 46073aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic return result; 46173aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic } 46273aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic 463563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic private void detectLaggyConnection(final int operation, final long timeout, final long startTime) { 46473aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final long duration = SystemClock.uptimeMillis() - startTime; 465563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic if (duration >= timeout) { 46673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final String operationName = OPERATION_NAMES[operation]; 46773aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic Log.w(TAG, "Slow InputConnection: " + operationName + " took " + duration + " ms."); 46873aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic StatsUtils.onInputConnectionLaggy(operation, duration); 469912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang mLastSlowInputConnectionTime = SystemClock.uptimeMillis(); 47073aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic } 4715475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 4725475b38328171a0841ae18074bd45380ec567e90Jean Chalard 4730232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic public void deleteTextBeforeCursor(final int beforeLength) { 474574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 475fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // TODO: the following is incorrect if the cursor is not immediately after the composition. 476fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // Right now we never come here in this case because we reset the composing state before we 477fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // come here in this case, but we need to fix this. 4788c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard final int remainingChars = mComposingText.length() - beforeLength; 47928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (remainingChars >= 0) { 48028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(remainingChars); 48128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } else { 48228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(0); 48328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // Never cut under 0 48428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final int len = Math.max(mCommittedTextBeforeComposingText.length() 48528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard + remainingChars, 0); 48628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mCommittedTextBeforeComposingText.setLength(len); 48728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 4882fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (mExpectedSelStart > beforeLength) { 4892fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart -= beforeLength; 4902fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd -= beforeLength; 49128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } else { 4922fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // There are fewer characters before the cursor in the buffer than we are being asked to 493e93a7232d17caffdca2ab3f62881ab0ae31f1cd5Jean Chalard // delete. Only delete what is there, and update the end with the amount deleted. 4942fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd -= mExpectedSelStart; 495e93a7232d17caffdca2ab3f62881ab0ae31f1cd5Jean Chalard mExpectedSelStart = 0; 49628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 497edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 4980232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic mIC.deleteSurroundingText(beforeLength, 0); 49994e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 50028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 5015475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 5025475b38328171a0841ae18074bd45380ec567e90Jean Chalard 5035475b38328171a0841ae18074bd45380ec567e90Jean Chalard public void performEditorAction(final int actionId) { 504f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 505edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 50694e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.performEditorAction(actionId); 50794e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 5085475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 5095475b38328171a0841ae18074bd45380ec567e90Jean Chalard 5105475b38328171a0841ae18074bd45380ec567e90Jean Chalard public void sendKeyEvent(final KeyEvent keyEvent) { 511574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 51228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 51328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 514b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa // This method is only called for enter or backspace when speaking to old applications 515b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa // (target SDK <= 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)), or for digits. 51628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // When talking to new applications we never use this method because it's inherently 51728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // racy and has unpredictable results, but for backward compatibility we continue 51828d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // sending the key events for only Enter and Backspace because some applications 51928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // mistakenly catch them to do some stuff. 52028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard switch (keyEvent.getKeyCode()) { 521b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa case KeyEvent.KEYCODE_ENTER: 522b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mCommittedTextBeforeComposingText.append("\n"); 5232fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart += 1; 5242fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 525b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa break; 526b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa case KeyEvent.KEYCODE_DEL: 527b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa if (0 == mComposingText.length()) { 528b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa if (mCommittedTextBeforeComposingText.length() > 0) { 529b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mCommittedTextBeforeComposingText.delete( 530b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mCommittedTextBeforeComposingText.length() - 1, 531b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mCommittedTextBeforeComposingText.length()); 53228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 533b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa } else { 534b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mComposingText.delete(mComposingText.length() - 1, mComposingText.length()); 535b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa } 5362fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (mExpectedSelStart > 0 && mExpectedSelStart == mExpectedSelEnd) { 5372fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // TODO: Handle surrogate pairs. 5382fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart -= 1; 5392fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa } 5402fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 541b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa break; 542b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa case KeyEvent.KEYCODE_UNKNOWN: 543b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa if (null != keyEvent.getCharacters()) { 544b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mCommittedTextBeforeComposingText.append(keyEvent.getCharacters()); 5452fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart += keyEvent.getCharacters().length(); 5462fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 547b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa } 548b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa break; 549b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa default: 5502fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar()); 551b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa mCommittedTextBeforeComposingText.append(text); 5522fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart += text.length(); 5532fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 554b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa break; 55528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 55628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 557edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 55894e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.sendKeyEvent(keyEvent); 55994e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 5605475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 5615475b38328171a0841ae18074bd45380ec567e90Jean Chalard 56218d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard public void setComposingRegion(final int start, final int end) { 56318d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 56418d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 56518d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard final CharSequence textBeforeCursor = 566f1d8aa46f9172c2d8864d0d2161aa8220d036cc9Jean Chalard getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0); 56718d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard mCommittedTextBeforeComposingText.setLength(0); 568740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard if (!TextUtils.isEmpty(textBeforeCursor)) { 569fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // The cursor is not necessarily at the end of the composing text, but we have its 570fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // position in mExpectedSelStart and mExpectedSelEnd. In this case we want the start 571fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // of the text, so we should use mExpectedSelStart. In other words, the composing 572fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // text starts (mExpectedSelStart - start) characters before the end of textBeforeCursor 573740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard final int indexOfStartOfComposingText = 574fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard Math.max(textBeforeCursor.length() - (mExpectedSelStart - start), 0); 575740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText, 576740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard textBeforeCursor.length())); 577740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard mCommittedTextBeforeComposingText.append( 578740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard textBeforeCursor.subSequence(0, indexOfStartOfComposingText)); 579740da0d8d0b29297fba75f4b63ccff28fc1f4a14Jean Chalard } 580edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 58118d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard mIC.setComposingRegion(start, end); 58218d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard } 58318d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard } 58418d688c94bb8e1e26de2d12445cb3096c6126f75Jean Chalard 5858c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard public void setComposingText(final CharSequence text, final int newCursorPosition) { 586574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 58728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 5882fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart += text.length() - mComposingText.length(); 5892fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 59028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(0); 59128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.append(text); 5922fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // TODO: support values of newCursorPosition != 1. At this time, this is never called with 5932fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // newCursorPosition != 1. 594edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 5958c6537edbd9e80b2d7169ecd31b1f0efbd1f9f20Jean Chalard mIC.setComposingText(text, newCursorPosition); 59694e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 59728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 5985475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 5995475b38328171a0841ae18074bd45380ec567e90Jean Chalard 6002fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa /** 6012fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * Set the selection of the text editor. 6022fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * 6032fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * Calls through to {@link InputConnection#setSelection(int, int)}. 6042fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * 6052fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param start the character index where the selection should start. 6062fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param end the character index where the selection should end. 607f19745728e7231ffc8d7774b32821f31473ce1beJean Chalard * @return Returns true on success, false on failure: either the input connection is no longer 608f19745728e7231ffc8d7774b32821f31473ce1beJean Chalard * valid when setting the selection or when retrieving the text cache at that point, or 609f19745728e7231ffc8d7774b32821f31473ce1beJean Chalard * invalid arguments were passed. 6102fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa */ 6112fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa public boolean setSelection(final int start, final int end) { 612574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 61328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 614f19745728e7231ffc8d7774b32821f31473ce1beJean Chalard if (start < 0 || end < 0) { 615f19745728e7231ffc8d7774b32821f31473ce1beJean Chalard return false; 616f19745728e7231ffc8d7774b32821f31473ce1beJean Chalard } 6172fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart = start; 6182fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = end; 619edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 6202fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final boolean isIcValid = mIC.setSelection(start, end); 6212fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (!isIcValid) { 6222fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return false; 6232fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa } 62494e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 6252fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return reloadTextCache(); 6265475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 6275475b38328171a0841ae18074bd45380ec567e90Jean Chalard 6285475b38328171a0841ae18074bd45380ec567e90Jean Chalard public void commitCorrection(final CorrectionInfo correctionInfo) { 629574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 63028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 63128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // This has no effect on the text field and does not change its content. It only makes 63228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard // TextView flash the text for a second based on indices contained in the argument. 633edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 63494e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.commitCorrection(correctionInfo); 63594e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 63628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 6375475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 6385475b38328171a0841ae18074bd45380ec567e90Jean Chalard 6395475b38328171a0841ae18074bd45380ec567e90Jean Chalard public void commitCompletion(final CompletionInfo completionInfo) { 640574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 64128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 64202ce3dc2d11aba2b521f85223af1f870207b81dcJean Chalard CharSequence text = completionInfo.getText(); 64302ce3dc2d11aba2b521f85223af1f870207b81dcJean Chalard // text should never be null, but just in case, it's better to insert nothing than to crash 64402ce3dc2d11aba2b521f85223af1f870207b81dcJean Chalard if (null == text) text = ""; 64528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mCommittedTextBeforeComposingText.append(text); 6462fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelStart += text.length() - mComposingText.length(); 6472fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa mExpectedSelEnd = mExpectedSelStart; 64828d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard mComposingText.setLength(0); 649edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (isConnected()) { 65094e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge mIC.commitCompletion(completionInfo); 65194e7f4bef970431f509a806d1b92b19fc3b5ce7dKurt Partridge } 65228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); 6535475b38328171a0841ae18074bd45380ec567e90Jean Chalard } 65402308bec632a5df23325c916bffec5def16b22b4Jean Chalard 6552da886651874b2588f18f800417ba858ac93d88bJean Chalard @SuppressWarnings("unused") 656d3a4c5132422b189c8dbb94dbbe84a9b9761b0a8Tadashi G. Takaoka @Nonnull 657bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagi public NgramContext getNgramContextFromNthPreviousWord( 65817f326b7458c2bde2569e283a96e703755485328Keisuke Kuroyanagi final SpacingAndPunctuations spacingAndPunctuations, final int n) { 659f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 660edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (!isConnected()) { 661bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagi return NgramContext.EMPTY_PREV_WORDS_INFO; 66217f326b7458c2bde2569e283a96e703755485328Keisuke Kuroyanagi } 6630232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic final CharSequence prev = getTextBeforeCursor(NUM_CHARS_TO_GET_BEFORE_CURSOR, 0); 66428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (DEBUG_PREVIOUS_TEXT && null != prev) { 6650232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic final int checkLength = NUM_CHARS_TO_GET_BEFORE_CURSOR - 1; 66628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final String reference = prev.length() <= checkLength ? prev.toString() 66728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard : prev.subSequence(prev.length() - checkLength, prev.length()).toString(); 668fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // TODO: right now the following works because mComposingText holds the part of the 669fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // composing text that is before the cursor, but this is very confusing. We should 670fe92c174ea08f9f593432f0ab20961700de9e027Jean Chalard // fix it. 67128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final StringBuilder internal = new StringBuilder() 67228d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard .append(mCommittedTextBeforeComposingText).append(mComposingText); 67328d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (internal.length() > checkLength) { 67428d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard internal.delete(0, internal.length() - checkLength); 67528d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard if (!(reference.equals(internal.toString()))) { 67628d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard final String context = 67728d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard "Expected text = " + internal + "\nActual text = " + reference; 67828d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); 67928d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 68028d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 68128d765ed901bfd1e736056db1cd807c13ef88c35Jean Chalard } 682bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagi return NgramContextUtils.getNgramContextFromNthPreviousWord( 683bb843eb223ce0f8fb1088ed3393a4165123ddb1fKeisuke Kuroyanagi prev, spacingAndPunctuations, n); 68402308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 68502308bec632a5df23325c916bffec5def16b22b4Jean Chalard 686914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard private static boolean isPartOfCompositionForScript(final int codePoint, 687914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { 688914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard // We always consider word connectors part of compositions. 689914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard return spacingAndPunctuations.isWordConnector(codePoint) 690914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard // Otherwise, it's part of composition if it's part of script and not a separator. 691914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard || (!spacingAndPunctuations.isWordSeparator(codePoint) 692914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard && ScriptUtils.isLetterPartOfScript(codePoint, scriptId)); 693914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard } 694914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard 69502308bec632a5df23325c916bffec5def16b22b4Jean Chalard /** 69602308bec632a5df23325c916bffec5def16b22b4Jean Chalard * Returns the text surrounding the cursor. 69702308bec632a5df23325c916bffec5def16b22b4Jean Chalard * 698914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard * @param spacingAndPunctuations the rules for spacing and punctuation 699292deb632cbab232334190e68d29184094d6d51bJean Chalard * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* 70002308bec632a5df23325c916bffec5def16b22b4Jean Chalard * @return a range containing the text surrounding the cursor 70102308bec632a5df23325c916bffec5def16b22b4Jean Chalard */ 702914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, 703914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard final int scriptId) { 704f254e3fec7744dc1eb2cc09ac157986c3b2b5408Jean Chalard mIC = mParent.getCurrentInputConnection(); 705edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (!isConnected()) { 70602308bec632a5df23325c916bffec5def16b22b4Jean Chalard return null; 70702308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 70873aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic final CharSequence before = getTextBeforeCursorAndDetectLaggyConnection( 70973aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic OPERATION_GET_WORD_RANGE_AT_CURSOR, 710563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, 71173aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic NUM_CHARS_TO_GET_BEFORE_CURSOR, 7126a114fa700d3ca73c608e1291b74bbbdd5a1a7b7Jean Chalard InputConnection.GET_TEXT_WITH_STYLES); 713912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang final CharSequence after = getTextAfterCursorAndDetectLaggyConnection( 71473aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic OPERATION_GET_WORD_RANGE_AT_CURSOR, 715563d7935a9e53100f6ee0ec41aff931fa28558baDan Zivkovic SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, 71673aaf6833780c1915dd4ab7d3f0f55e3af130f99Dan Zivkovic NUM_CHARS_TO_GET_AFTER_CURSOR, 7176a114fa700d3ca73c608e1291b74bbbdd5a1a7b7Jean Chalard InputConnection.GET_TEXT_WITH_STYLES); 71802308bec632a5df23325c916bffec5def16b22b4Jean Chalard if (before == null || after == null) { 71902308bec632a5df23325c916bffec5def16b22b4Jean Chalard return null; 72002308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 72102308bec632a5df23325c916bffec5def16b22b4Jean Chalard 722b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard // Going backward, find the first breaking point (separator) 7230abc48218ee90b1d8df77dfa131ce05fbaba7121Jean Chalard int startIndexInBefore = before.length(); 724b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard while (startIndexInBefore > 0) { 725b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard final int codePoint = Character.codePointBefore(before, startIndexInBefore); 726914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { 727b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard break; 72802308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 729b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard --startIndexInBefore; 730b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard if (Character.isSupplementaryCodePoint(codePoint)) { 731b6695867a5fe6af999d23c669e2e6a6182457bbaJean Chalard --startIndexInBefore; 73202308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 73302308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 73402308bec632a5df23325c916bffec5def16b22b4Jean Chalard 73502308bec632a5df23325c916bffec5def16b22b4Jean Chalard // Find last word separator after the cursor 7360abc48218ee90b1d8df77dfa131ce05fbaba7121Jean Chalard int endIndexInAfter = -1; 7370abc48218ee90b1d8df77dfa131ce05fbaba7121Jean Chalard while (++endIndexInAfter < after.length()) { 7380abc48218ee90b1d8df77dfa131ce05fbaba7121Jean Chalard final int codePoint = Character.codePointAt(after, endIndexInAfter); 739914078fd9198aeb3d7ffa034562321d688d588f7Jean Chalard if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { 74002308bec632a5df23325c916bffec5def16b22b4Jean Chalard break; 74102308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 74202308bec632a5df23325c916bffec5def16b22b4Jean Chalard if (Character.isSupplementaryCodePoint(codePoint)) { 7430abc48218ee90b1d8df77dfa131ce05fbaba7121Jean Chalard ++endIndexInAfter; 74402308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 74502308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 74602308bec632a5df23325c916bffec5def16b22b4Jean Chalard 7477a7aeffcdcf1f25abdc3923e81fa19c2258fa9e9Jean Chalard final boolean hasUrlSpans = 7487a7aeffcdcf1f25abdc3923e81fa19c2258fa9e9Jean Chalard SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length()) 7497a7aeffcdcf1f25abdc3923e81fa19c2258fa9e9Jean Chalard || SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter); 7503d68b066626d7e58cbe2853cd186b1ad75b90259Jean Chalard // We don't use TextUtils#concat because it copies all spans without respect to their 7513d68b066626d7e58cbe2853cd186b1ad75b90259Jean Chalard // nature. If the text includes a PARAGRAPH span and it has been split, then 7523d68b066626d7e58cbe2853cd186b1ad75b90259Jean Chalard // TextUtils#concat will crash when it tries to concat both sides of it. 753f56b82f80961a511765df206eee36229cbee6ed8Ken Wakasa return new TextRange( 754f56b82f80961a511765df206eee36229cbee6ed8Ken Wakasa SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after), 7557a7aeffcdcf1f25abdc3923e81fa19c2258fa9e9Jean Chalard startIndexInBefore, before.length() + endIndexInAfter, before.length(), 7567a7aeffcdcf1f25abdc3923e81fa19c2258fa9e9Jean Chalard hasUrlSpans); 75702308bec632a5df23325c916bffec5def16b22b4Jean Chalard } 75802308bec632a5df23325c916bffec5def16b22b4Jean Chalard 759912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations, 760912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang boolean checkTextAfter) { 761912016b69f1c0e26ec58ee9d17c8ac7e5711d70dTom Ouyang if (checkTextAfter && isCursorFollowedByWordCharacter(spacingAndPunctuations)) { 76272b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard // If what's after the cursor is a word character, then we're touching a word. 76372b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard return true; 76472b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard } 76572b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard final String textBeforeCursor = mCommittedTextBeforeComposingText.toString(); 76672b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard int indexOfCodePointInJavaChars = textBeforeCursor.length(); 76772b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE 76872b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars); 76972b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard // Search for the first non word-connector char 77072b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) { 77172b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint); 77272b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE 77372b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars); 77472b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard } 77572b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard return !(Constants.NOT_A_CODE == consideredCodePoint 77672b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard || spacingAndPunctuations.isWordSeparator(consideredCodePoint) 77772b67f65411cf07cb8cb2d52e859f46d9d5b91d4Jean Chalard || spacingAndPunctuations.isWordConnector(consideredCodePoint)); 778e5cdcaff658f5366621115a728cb683eab5fda0aJean Chalard } 779e5cdcaff658f5366621115a728cb683eab5fda0aJean Chalard 7806a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka public boolean isCursorFollowedByWordCharacter( 7816a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka final SpacingAndPunctuations spacingAndPunctuations) { 7820a064a5d88c5cdc03f8ef13a2f7b6b506b9cbdfaJean Chalard final CharSequence after = getTextAfterCursor(1, 0); 7836a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka if (TextUtils.isEmpty(after)) { 7846a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka return false; 7856a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka } 7866a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka final int codePointAfterCursor = Character.codePointAt(after, 0); 7876a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka if (spacingAndPunctuations.isWordSeparator(codePointAfterCursor) 7886a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka || spacingAndPunctuations.isWordConnector(codePointAfterCursor)) { 7896a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka return false; 790bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard } 7916a18af634eda872daad858acbddae2a15452952eTadashi G. Takaoka return true; 792bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard } 793bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard 794bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard public void removeTrailingSpace() { 795574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 7960a064a5d88c5cdc03f8ef13a2f7b6b506b9cbdfaJean Chalard final int codePointBeforeCursor = getCodePointBeforeCursor(); 7970a064a5d88c5cdc03f8ef13a2f7b6b506b9cbdfaJean Chalard if (Constants.CODE_SPACE == codePointBeforeCursor) { 7980232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic deleteTextBeforeCursor(1); 799bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard } 800bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard } 801bbbdab12be748cdc2158f0e04bbb5478052ecd89Jean Chalard 802747cf0435a7e978dfd43c30bd931b56146c3d852Jean Chalard public boolean sameAsTextBeforeCursor(final CharSequence text) { 803747cf0435a7e978dfd43c30bd931b56146c3d852Jean Chalard final CharSequence beforeText = getTextBeforeCursor(text.length(), 0); 804747cf0435a7e978dfd43c30bd931b56146c3d852Jean Chalard return TextUtils.equals(text, beforeText); 805747cf0435a7e978dfd43c30bd931b56146c3d852Jean Chalard } 806747cf0435a7e978dfd43c30bd931b56146c3d852Jean Chalard 807b41ee671944aee97163c1567d7407fc3bd7507a3Jean Chalard public boolean revertDoubleSpacePeriod(final SpacingAndPunctuations spacingAndPunctuations) { 808574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 809a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard // Here we test whether we indeed have a period and a space before us. This should not 810a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard // be needed, but it's there just in case something went wrong. 811a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); 812b41ee671944aee97163c1567d7407fc3bd7507a3Jean Chalard if (!TextUtils.equals(spacingAndPunctuations.mSentenceSeparatorAndSpace, 813b41ee671944aee97163c1567d7407fc3bd7507a3Jean Chalard textBeforeCursor)) { 814a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard // Theoretically we should not be coming here if there isn't ". " before the 815a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard // cursor, but the application may be changing the text while we are typing, so 816a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard // anything goes. We should not crash. 817b41ee671944aee97163c1567d7407fc3bd7507a3Jean Chalard Log.d(TAG, "Tried to revert double-space combo but we didn't find \"" 818b41ee671944aee97163c1567d7407fc3bd7507a3Jean Chalard + spacingAndPunctuations.mSentenceSeparatorAndSpace 819b41ee671944aee97163c1567d7407fc3bd7507a3Jean Chalard + "\" just before the cursor."); 820a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard return false; 821a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard } 822522d13c302e360cd4984b9164a9fc81bdc64feacJean Chalard // Double-space results in ". ". A backspace to cancel this should result in a single 823522d13c302e360cd4984b9164a9fc81bdc64feacJean Chalard // space in the text field, so we replace ". " with a single space. 8240232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic deleteTextBeforeCursor(2); 825522d13c302e360cd4984b9164a9fc81bdc64feacJean Chalard final String singleSpace = " "; 826522d13c302e360cd4984b9164a9fc81bdc64feacJean Chalard commitText(singleSpace, 1); 827a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard return true; 828a32eb2721390d5964c83c787ad30fd3f61b936b0Jean Chalard } 8292010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard 8302010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard public boolean revertSwapPunctuation() { 831574b80aacee95df26e85e6b78876a73d7076a672Jean Chalard if (DEBUG_BATCH_NESTING) checkBatchEdit(); 8322010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // Here we test whether we indeed have a space and something else before us. This should not 8332010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // be needed, but it's there just in case something went wrong. 8342010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); 8352010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to 8362010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // enter surrogate pairs this code will have been removed. 8372010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard if (TextUtils.isEmpty(textBeforeCursor) 838240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka || (Constants.CODE_SPACE != textBeforeCursor.charAt(1))) { 8392010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // We may only come here if the application is changing the text while we are typing. 8402010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // This is quite a broken case, but not logically impossible, so we shouldn't crash, 8412010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard // but some debugging log may be in order. 8422010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard Log.d(TAG, "Tried to revert a swap of punctuation but we didn't " 8432010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard + "find a space just before the cursor."); 8442010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard return false; 8452010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard } 8460232e73dfa5e7cadf3f0698407fe6aecd97f3227Dan Zivkovic deleteTextBeforeCursor(2); 847f763dc5915d394378bdcdc90cc0b238e66926b8bKurt Partridge final String text = " " + textBeforeCursor.subSequence(0, 1); 848f763dc5915d394378bdcdc90cc0b238e66926b8bKurt Partridge commitText(text, 1); 8492010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard return true; 8502010aad741bc1a7266913bcb8b8348d6e401c95bJean Chalard } 8515ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard 8525ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard /** 8535ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * Heuristic to determine if this is an expected update of the cursor. 8545ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * 8555ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * Sometimes updates to the cursor position are late because of their asynchronous nature. 8565ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * This method tries to determine if this update is one, based on the values of the cursor 8575ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * position in the update, and the currently expected position of the cursor according to 8585ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * LatinIME's internal accounting. If this is not a belated expected update, then it should 8595ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * mean that the user moved the cursor explicitly. 8605ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * This is quite robust, but of course it's not perfect. In particular, it will fail in the 8615ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * case we get an update A, the user types in N characters so as to move the cursor to A+N but 8625ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * we don't get those, and then the user places the cursor between A and A+N, and we get only 8635ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * this update and not the ones in-between. This is almost impossible to achieve even trying 8645ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * very very hard. 8655ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * 8662fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param oldSelStart The value of the old selection in the update. 8672fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param newSelStart The value of the new selection in the update. 8682fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param oldSelEnd The value of the old selection end in the update. 8692fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * @param newSelEnd The value of the new selection end in the update. 8705ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard * @return whether this is a belated expected update or not. 8715ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard */ 8722fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart, 8732fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa final int oldSelEnd, final int newSelEnd) { 874ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // This update is "belated" if we are expecting it. That is, mExpectedSelStart and 8752fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // mExpectedSelEnd match the new values that the TextView is updating TO. 8762fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true; 877ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // This update is not belated if mExpectedSelStart and mExpectedSelEnd match the old 878ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // values, and one of newSelStart or newSelEnd is updated to a different value. In this 879be8ad77ebb5d78db18a2ca1992e5e73d2b5e158aJean Chalard // case, it is likely that something other than the IME has moved the selection endpoint 8802fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // to the new value. 8812fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd 8822fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false; 883be8ad77ebb5d78db18a2ca1992e5e73d2b5e158aJean Chalard // If neither of the above two cases hold, then the system may be having trouble keeping up 884ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart 8852fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then 8862fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa // assume a belated update. 8872fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return (newSelStart == newSelEnd) 8882fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa && (newSelStart - oldSelStart) * (mExpectedSelStart - newSelStart) >= 0 8892fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa && (newSelEnd - oldSelEnd) * (mExpectedSelEnd - newSelEnd) >= 0; 8905ed88457bf9ef3305d4a5aa4ac05b513433ad0ddJean Chalard } 89173ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard 89273ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard /** 89373ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard * Looks at the text just before the cursor to find out if it looks like a URL. 89473ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard * 89573ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard * The weakest point here is, if we don't have enough text bufferized, we may fail to realize 89673ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard * we are in URL situation, but other places in this class have the same limitation and it 89773ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard * does not matter too much in the practice. 89873ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard */ 89973ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard public boolean textBeforeCursorLooksLikeURL() { 90073ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText); 90173ec85b8ad3102ce1c7e6013be73afe83475e589Jean Chalard } 902ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard 903ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard /** 904c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard * Looks at the text just before the cursor to find out if we are inside a double quote. 905c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard * 906c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard * As with #textBeforeCursorLooksLikeURL, this is dependent on how much text we have cached. 907c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard * However this won't be a concrete problem in most situations, as the cache is almost always 908c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard * long enough for this use. 909c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard */ 910c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard public boolean isInsideDoubleQuoteOrAfterDigit() { 911c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard return StringUtils.isInsideDoubleQuoteOrAfterDigit(mCommittedTextBeforeComposingText); 912c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard } 913c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard 914c7ef305bbc119b820fd619d3ed205198d4f98c3fJean Chalard /** 915ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard * Try to get the text from the editor to expose lies the framework may have been 91638144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard * telling us. Concretely, when the device rotates and when the keyboard reopens in the same 91738144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard * text field after having been closed with the back key, the frameworks tells us about where 91838144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard * the cursor used to be initially in the editor at the time it first received the focus; this 919ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard * may be completely different from the place it is upon rotation. Since we don't have any 920ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard * means to get the real value, try at least to ask the text view for some characters and 921ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard * detect the most damaging cases: when the cursor position is declared to be much smaller 922ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard * than it really is. 923ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard */ 924ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard public void tryFixLyingCursorPosition() { 9257d74594123bd9ee3a09f54d509bff27af5f6cca8Jean Chalard mIC = mParent.getCurrentInputConnection(); 926ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard final CharSequence textBeforeCursor = getTextBeforeCursor( 927ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); 928edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic final CharSequence selectedText = isConnected() ? mIC.getSelectedText(0 /* flags */) : null; 92938144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard if (null == textBeforeCursor || 93038144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard (!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) { 93138144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // If textBeforeCursor is null, we have no idea what kind of text field we have or if 93238144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // thinking about the "cursor position" actually makes any sense. In this case we 93338144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // remember a meaningless cursor position. Contrast this with an empty string, which is 93438144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // valid and should mean the cursor is at the start of the text. 93538144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // Also, if we expect we don't have a selection but we DO have non-empty selected text, 93638144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // then the framework lied to us about the cursor position. In this case, we should just 93738144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // revert to the most basic behavior possible for the next action (backspace in 93838144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // particular comes to mind), so we remember a meaningless cursor position which should 93938144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // result in degraded behavior from the next input. 94038144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // Interestingly, in either case, chances are any action the user takes next will result 94138144047ea3985c9345e7fbe6bb3aafbeaea5f06Jean Chalard // in a call to onUpdateSelection, which should set things right. 942ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION; 943ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } else { 944ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard final int textLength = textBeforeCursor.length(); 9453b4c1d30565c06a7f5a4c362002862e8bcb61f01Jean Chalard if (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE 9463b4c1d30565c06a7f5a4c362002862e8bcb61f01Jean Chalard && (textLength > mExpectedSelStart 9473b4c1d30565c06a7f5a4c362002862e8bcb61f01Jean Chalard || mExpectedSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) { 948ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // It should not be possible to have only one of those variables be 949ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized 950ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // (simple cursor, no selection) or there is no cursor/we don't know its pos 951ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard final boolean wasEqual = mExpectedSelStart == mExpectedSelEnd; 952ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard mExpectedSelStart = textLength; 953ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // We can't figure out the value of mLastSelectionEnd :( 954ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // But at least if it's smaller than mLastSelectionStart something is wrong, 955ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // and if they used to be equal we also don't want to make it look like there is a 956ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard // selection. 957ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard if (wasEqual || mExpectedSelStart > mExpectedSelEnd) { 958ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard mExpectedSelEnd = mExpectedSelStart; 959ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 960ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 961ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 962ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 963ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard 964c92c883fdf2287b49392692fa2e8d109dc26f785David Faden @Override 965c92c883fdf2287b49392692fa2e8d109dc26f785David Faden public boolean performPrivateCommand(final String action, final Bundle data) { 966c92c883fdf2287b49392692fa2e8d109dc26f785David Faden mIC = mParent.getCurrentInputConnection(); 967edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (!isConnected()) { 968c92c883fdf2287b49392692fa2e8d109dc26f785David Faden return false; 969c92c883fdf2287b49392692fa2e8d109dc26f785David Faden } 970c92c883fdf2287b49392692fa2e8d109dc26f785David Faden return mIC.performPrivateCommand(action, data); 971c92c883fdf2287b49392692fa2e8d109dc26f785David Faden } 972c92c883fdf2287b49392692fa2e8d109dc26f785David Faden 973ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard public int getExpectedSelectionStart() { 974ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard return mExpectedSelStart; 975ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 976ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard 977ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard public int getExpectedSelectionEnd() { 978ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard return mExpectedSelEnd; 979ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 980ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard 981ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard /** 982ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard * @return whether there is a selection currently active. 983ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard */ 984ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard public boolean hasSelection() { 985ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard return mExpectedSelEnd != mExpectedSelStart; 986ecf46df22683b997311ba77b34b33dffa8be43c0Jean Chalard } 9879fd9a68d8797ed500d07d5e149cd4da50be2df15Jean Chalard 9889fd9a68d8797ed500d07d5e149cd4da50be2df15Jean Chalard public boolean isCursorPositionKnown() { 9899fd9a68d8797ed500d07d5e149cd4da50be2df15Jean Chalard return INVALID_CURSOR_POSITION != mExpectedSelStart; 9909fd9a68d8797ed500d07d5e149cd4da50be2df15Jean Chalard } 9919273f3832b51f5d23d86df624600381ed6d6585fJean Chalard 9929273f3832b51f5d23d86df624600381ed6d6585fJean Chalard /** 9939273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * Work around a bug that was present before Jelly Bean upon rotation. 9949273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * 9959273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * Before Jelly Bean, there is a bug where setComposingRegion and other committing 9969273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * functions on the input connection get ignored until the cursor moves. This method works 9979273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * around the bug by wiggling the cursor first, which reactivates the connection and has 9989273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * the subsequent methods work, then restoring it to its original position. 9999273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * 10009273f3832b51f5d23d86df624600381ed6d6585fJean Chalard * On platforms on which this method is not present, this is a no-op. 10019273f3832b51f5d23d86df624600381ed6d6585fJean Chalard */ 10029273f3832b51f5d23d86df624600381ed6d6585fJean Chalard public void maybeMoveTheCursorAroundAndRestoreToWorkaroundABug() { 10035254c01d4cc024527479d4dc5fab2ed2516c395cDan Zivkovic if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 10049273f3832b51f5d23d86df624600381ed6d6585fJean Chalard if (mExpectedSelStart > 0) { 10059273f3832b51f5d23d86df624600381ed6d6585fJean Chalard mIC.setSelection(mExpectedSelStart - 1, mExpectedSelStart - 1); 10069273f3832b51f5d23d86df624600381ed6d6585fJean Chalard } else { 10079273f3832b51f5d23d86df624600381ed6d6585fJean Chalard mIC.setSelection(mExpectedSelStart + 1, mExpectedSelStart + 1); 10089273f3832b51f5d23d86df624600381ed6d6585fJean Chalard } 10099273f3832b51f5d23d86df624600381ed6d6585fJean Chalard mIC.setSelection(mExpectedSelStart, mExpectedSelEnd); 10109273f3832b51f5d23d86df624600381ed6d6585fJean Chalard } 10119273f3832b51f5d23d86df624600381ed6d6585fJean Chalard } 101229200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa 101329200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa /** 101429200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * Requests the editor to call back {@link InputMethodManager#updateCursorAnchorInfo}. 101529200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * @param enableMonitor {@code true} to request the editor to call back the method whenever the 101629200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * cursor/anchor position is changed. 101729200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * @param requestImmediateCallback {@code true} to request the editor to call back the method 101829200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * as soon as possible to notify the current cursor/anchor position to the input method. 101929200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * @return {@code true} if the request is accepted. Returns {@code false} otherwise, which 102029200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * includes "not implemented" or "rejected" or "temporarily unavailable" or whatever which 102129200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * prevents the application from fulfilling the request. (TODO: Improve the API when it turns 102229200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa * out that we actually need more detailed error codes) 102329200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa */ 10245d6ac77732b6fe29489deecc297d771642150a2bYohei Yukawa public boolean requestCursorUpdates(final boolean enableMonitor, 102529200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa final boolean requestImmediateCallback) { 1026acce1aa59eac6816fe3ce1fcb014666fc71a40f1Yohei Yukawa mIC = mParent.getCurrentInputConnection(); 1027edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic if (!isConnected()) { 1028a6d2cf5ec71fdba86455ff8281a121f32a3248e3Dan Zivkovic return false; 1029acce1aa59eac6816fe3ce1fcb014666fc71a40f1Yohei Yukawa } 1030a6d2cf5ec71fdba86455ff8281a121f32a3248e3Dan Zivkovic return InputConnectionCompatUtils.requestCursorUpdates( 1031a6d2cf5ec71fdba86455ff8281a121f32a3248e3Dan Zivkovic mIC, enableMonitor, requestImmediateCallback); 103229200b0abe1d65aa2f9ddefd247ab91563d666f8Yohei Yukawa } 10335475b38328171a0841ae18074bd45380ec567e90Jean Chalard} 1034