Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/input/AdapterInputConnection.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/input/AdapterInputConnection.java b/content/public/android/java/src/org/chromium/content/browser/input/AdapterInputConnection.java |
| index b0f8f3f05dfcef1dfbffb4de61ad63974df35519..2dcde91fc7f653bdf73ddfdb247d1df1ef80d919 100644 |
| --- a/content/public/android/java/src/org/chromium/content/browser/input/AdapterInputConnection.java |
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/AdapterInputConnection.java |
| @@ -5,9 +5,7 @@ |
| package org.chromium.content.browser.input; |
| import android.text.Editable; |
| -import android.text.InputType; |
| import android.text.Selection; |
| -import android.util.StringBuilderPrinter; |
| import android.view.KeyCharacterMap; |
| import android.view.KeyEvent; |
| import android.view.View; |
| @@ -17,138 +15,94 @@ import android.view.inputmethod.ExtractedText; |
| import android.view.inputmethod.ExtractedTextRequest; |
| import org.chromium.base.Log; |
| +import org.chromium.base.ThreadUtils; |
| import org.chromium.base.VisibleForTesting; |
| -import org.chromium.blink_public.web.WebTextInputFlags; |
| -import org.chromium.ui.base.ime.TextInputType; |
| - |
| -import java.util.Locale; |
| /** |
| * InputConnection is created by ContentView.onCreateInputConnection. |
| * It then adapts android's IME to chrome's RenderWidgetHostView using the |
| * native ImeAdapterAndroid via the class ImeAdapter. |
| */ |
| -public class AdapterInputConnection extends BaseInputConnection { |
| +public class AdapterInputConnection |
|
aelias_OOO_until_Jul13
2016/01/21 07:43:04
Could you rename this class and file to ReplicaInp
Changwan Ryu
2016/01/22 10:22:16
Done.
|
| + extends BaseInputConnection implements ChromiumBaseInputConnection { |
| private static final String TAG = "cr_Ime"; |
| /** |
| * Selection value should be -1 if not known. See EditorInfo.java for details. |
| */ |
| public static final int INVALID_SELECTION = -1; |
| + |
| public static final int INVALID_COMPOSITION = -1; |
| private final ImeAdapter mImeAdapter; |
| + // This holds the state of editable text (e.g. contents of <input>, contenteditable) of |
| + // a focused element. |
| + // Every time the user, IME, javascript (Blink), autofill etc. modifies the content, the new |
| + // state must be reflected to this to keep consistency. |
| + private final Editable mEditable; |
| + |
| private boolean mSingleLine; |
| private int mNumNestedBatchEdits = 0; |
| private int mPendingAccent; |
| + private final ThreadManager mThreadManager; |
| private int mLastUpdateSelectionStart = INVALID_SELECTION; |
| private int mLastUpdateSelectionEnd = INVALID_SELECTION; |
| private int mLastUpdateCompositionStart = INVALID_COMPOSITION; |
| private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; |
| - @VisibleForTesting |
| - AdapterInputConnection(View view, ImeAdapter imeAdapter, int initialSelStart, int initialSelEnd, |
| - EditorInfo outAttrs) { |
| - super(view, true); |
| - mImeAdapter = imeAdapter; |
| - mImeAdapter.setInputConnection(this); |
| - |
| - mSingleLine = true; |
| - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN |
| - | EditorInfo.IME_FLAG_NO_EXTRACT_UI; |
| - outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT |
| - | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; |
| - |
| - int inputType = imeAdapter.getTextInputType(); |
| - int inputFlags = imeAdapter.getTextInputFlags(); |
| - if ((inputFlags & WebTextInputFlags.AutocompleteOff) != 0) { |
| - outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS; |
| + /** |
| + * Default factory for AdapterInputConnection classes. |
| + */ |
| + static class Factory implements ChromiumBaseInputConnection.Factory { |
| + // Note: we share Editable among input connections so that data remains the same on |
| + // switching inputs. However, the downside is that initial value cannot be correct, and |
| + // wrong value will be used when jumping from one input to another. |
| + protected final Editable mEditable; |
| + private final ThreadManager mThreadManager; |
| + |
| + Factory() { |
| + mThreadManager = new ThreadManager(); |
| + mEditable = Editable.Factory.getInstance().newEditable(""); |
| + Selection.setSelection(mEditable, 0); |
| } |
| - if (inputType == TextInputType.TEXT) { |
| - // Normal text field |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| - if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { |
| - outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; |
| - } |
| - } else if (inputType == TextInputType.TEXT_AREA |
| - || inputType == TextInputType.CONTENT_EDITABLE) { |
| - outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; |
| - if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { |
| - outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; |
| - } |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE; |
| - mSingleLine = false; |
| - } else if (inputType == TextInputType.PASSWORD) { |
| - // Password |
| - outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
| - | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| - } else if (inputType == TextInputType.SEARCH) { |
| - // Search |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH; |
| - } else if (inputType == TextInputType.URL) { |
| - // Url |
| - outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
| - | InputType.TYPE_TEXT_VARIATION_URI; |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| - } else if (inputType == TextInputType.EMAIL) { |
| - outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
| - | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; |
| - } else if (inputType == TextInputType.TELEPHONE) { |
| - // Telephone |
| - // Number and telephone do not have both a Tab key and an |
| - // action in default OSK, so set the action to NEXT |
| - outAttrs.inputType = InputType.TYPE_CLASS_PHONE; |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; |
| - } else if (inputType == TextInputType.NUMBER) { |
| - // Number |
| - outAttrs.inputType = InputType.TYPE_CLASS_NUMBER |
| - | InputType.TYPE_NUMBER_VARIATION_NORMAL |
| - | InputType.TYPE_NUMBER_FLAG_DECIMAL; |
| - outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; |
| + @Override |
| + public AdapterInputConnection get(View view, ImeAdapter imeAdapter, int inputType, |
| + int inputFlags, EditorInfo outAttrs) { |
| + return new AdapterInputConnection( |
| + view, imeAdapter, mThreadManager, mEditable, inputType, inputFlags, outAttrs); |
| } |
| + } |
| - // Handling of autocapitalize. Blink will send the flag taking into account the element's |
| - // type. This is not using AutocapitalizeNone because Android does not autocapitalize by |
| - // default and there is no way to express no capitalization. |
| - // Autocapitalize is meant as a hint to the virtual keyboard. |
| - if ((inputFlags & WebTextInputFlags.AutocapitalizeCharacters) != 0) { |
| - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; |
| - } else if ((inputFlags & WebTextInputFlags.AutocapitalizeWords) != 0) { |
| - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS; |
| - } else if ((inputFlags & WebTextInputFlags.AutocapitalizeSentences) != 0) { |
| - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; |
| - } |
| - // Content editable doesn't use autocapitalize so we need to set it manually. |
| - if (inputType == TextInputType.CONTENT_EDITABLE) { |
| - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; |
| + static class ThreadManager implements ChromiumBaseInputConnection.ThreadManager { |
| + @Override |
| + public boolean runningOnThisThread() { |
| + return ThreadUtils.runningOnUiThread(); |
| } |
| - outAttrs.initialSelStart = initialSelStart; |
| - outAttrs.initialSelEnd = initialSelEnd; |
| - mLastUpdateSelectionStart = outAttrs.initialSelStart; |
| - mLastUpdateSelectionEnd = outAttrs.initialSelEnd; |
| - Log.d(TAG, "Constructor called with outAttrs: %s", dumpEditorInfo(outAttrs)); |
| + @Override |
| + public void post(Runnable runnable) { |
| + ThreadUtils.postOnUiThread(runnable); |
| + } |
| } |
| - private static String dumpEditorInfo(EditorInfo editorInfo) { |
| - StringBuilder builder = new StringBuilder(); |
| - StringBuilderPrinter printer = new StringBuilderPrinter(builder); |
| - editorInfo.dump(printer, ""); |
| - return builder.toString(); |
| - } |
| + @VisibleForTesting |
| + AdapterInputConnection(View view, ImeAdapter imeAdapter, ThreadManager threadManager, |
| + Editable editable, int inputType, int inputFlags, EditorInfo outAttrs) { |
| + super(view, true); |
| + mImeAdapter = imeAdapter; |
| + mEditable = editable; |
| + mThreadManager = threadManager; |
| + |
| + int initialSelStart = Selection.getSelectionStart(editable); |
| + int initialSelEnd = Selection.getSelectionEnd(editable); |
| + ImeUtils.computeEditorInfo(inputType, inputFlags, initialSelStart, initialSelEnd, outAttrs); |
| - private static String dumpEditable(Editable editable) { |
| - return String.format(Locale.US, "Editable {[%s] SEL[%d %d] COM[%d %d]}", |
| - editable.toString(), |
| - Selection.getSelectionStart(editable), |
| - Selection.getSelectionEnd(editable), |
| - getComposingSpanStart(editable), |
| - getComposingSpanEnd(editable)); |
| + mLastUpdateSelectionStart = initialSelStart; |
| + mLastUpdateSelectionEnd = initialSelEnd; |
| + |
| + Log.d(TAG, "Constructor called with outAttrs: %s", ImeUtils.dumpEditorInfo(outAttrs)); |
| } |
| /** |
| @@ -168,11 +122,13 @@ public class AdapterInputConnection extends BaseInputConnection { |
| * selection. |
| * @param isNonImeChange True when the update was caused by non-IME (e.g. Javascript). |
| */ |
| - @VisibleForTesting |
| - public void updateState(String text, int selectionStart, int selectionEnd, int compositionStart, |
| - int compositionEnd, boolean isNonImeChange) { |
| - Log.d(TAG, "updateState [%s] [%s %s] [%s %s] [%b]", text, selectionStart, |
| - selectionEnd, compositionStart, compositionEnd, isNonImeChange); |
| + @Override |
| + public void updateStateOnUiThread(String text, int selectionStart, int selectionEnd, |
| + int compositionStart, int compositionEnd, boolean singleLine, boolean isNonImeChange) { |
| + Log.d(TAG, "updateState [%s] [%s %s] [%s %s] [%b] [%b]", text, selectionStart, |
| + selectionEnd, compositionStart, compositionEnd, singleLine, isNonImeChange); |
| + mSingleLine = singleLine; |
| + |
| // If this update is from the IME, no further state modification is necessary because the |
| // state should have been updated already by the IM framework directly. |
| if (!isNonImeChange) return; |
| @@ -185,19 +141,17 @@ public class AdapterInputConnection extends BaseInputConnection { |
| compositionStart = Math.min(compositionStart, text.length()); |
| compositionEnd = Math.min(compositionEnd, text.length()); |
| - Editable editable = getEditableInternal(); |
| - |
| - String prevText = editable.toString(); |
| + String prevText = mEditable.toString(); |
| boolean textUnchanged = prevText.equals(text); |
| if (!textUnchanged) { |
| - editable.replace(0, editable.length(), text); |
| + mEditable.replace(0, mEditable.length(), text); |
| } |
| - Selection.setSelection(editable, selectionStart, selectionEnd); |
| + Selection.setSelection(mEditable, selectionStart, selectionEnd); |
| if (compositionStart == compositionEnd) { |
| - removeComposingSpans(editable); |
| + removeComposingSpans(mEditable); |
| } else { |
| super.setComposingRegion(compositionStart, compositionEnd); |
| } |
| @@ -209,13 +163,8 @@ public class AdapterInputConnection extends BaseInputConnection { |
| */ |
| @Override |
| public Editable getEditable() { |
| - Editable editable = getEditableInternal(); |
| - Log.d(TAG, "getEditable: %s", dumpEditable(editable)); |
| - return editable; |
| - } |
| - |
| - private Editable getEditableInternal() { |
| - return mImeAdapter.getEditable(); |
| + Log.d(TAG, "getEditable: %s", ImeUtils.dumpEditable(mEditable)); |
| + return mEditable; |
| } |
| /** |
| @@ -224,11 +173,10 @@ public class AdapterInputConnection extends BaseInputConnection { |
| */ |
| private void updateSelectionIfRequired() { |
| if (mNumNestedBatchEdits != 0) return; |
| - Editable editable = getEditableInternal(); |
| - int selectionStart = Selection.getSelectionStart(editable); |
| - int selectionEnd = Selection.getSelectionEnd(editable); |
| - int compositionStart = getComposingSpanStart(editable); |
| - int compositionEnd = getComposingSpanEnd(editable); |
| + int selectionStart = Selection.getSelectionStart(mEditable); |
| + int selectionEnd = Selection.getSelectionEnd(mEditable); |
| + int compositionStart = getComposingSpanStart(mEditable); |
| + int compositionEnd = getComposingSpanEnd(mEditable); |
| // Avoid sending update if we sent an exact update already previously. |
| if (mLastUpdateSelectionStart == selectionStart |
| && mLastUpdateSelectionEnd == selectionEnd |
| @@ -255,10 +203,9 @@ public class AdapterInputConnection extends BaseInputConnection { |
| Log.d(TAG, "setComposingText [%s] [%d]", text, newCursorPosition); |
| mPendingAccent = 0; |
| - Editable editable = getEditableInternal(); |
| - int selectionStartAfterReplacement = BaseInputConnection.getComposingSpanStart(editable); |
| + int selectionStartAfterReplacement = BaseInputConnection.getComposingSpanStart(mEditable); |
| if (selectionStartAfterReplacement == INVALID_COMPOSITION) { |
| - selectionStartAfterReplacement = Selection.getSelectionStart(editable); |
| + selectionStartAfterReplacement = Selection.getSelectionStart(mEditable); |
| } |
| int selectionEndAfterReplacement = selectionStartAfterReplacement + text.length(); |
| @@ -268,13 +215,13 @@ public class AdapterInputConnection extends BaseInputConnection { |
| // may not be correct when newCursorPosition != 1. We fix it here: |
| if (newCursorPosition > 1) { |
| int newPos = selectionEndAfterReplacement - 1 + newCursorPosition; |
| - int len = editable.length(); |
| + int len = mEditable.length(); |
| if (newPos > len) newPos = len; |
| - Selection.setSelection(editable, newPos); |
| + Selection.setSelection(mEditable, newPos); |
| } else if (newCursorPosition <= 0) { |
| int newPos = selectionStartAfterReplacement + newCursorPosition; |
| if (newPos < 0) newPos = 0; |
| - Selection.setSelection(editable, newPos); |
| + Selection.setSelection(mEditable, newPos); |
| } |
| updateSelectionIfRequired(); |
| @@ -327,12 +274,11 @@ public class AdapterInputConnection extends BaseInputConnection { |
| @Override |
| public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { |
| Log.d(TAG, "getExtractedText"); |
| - Editable editable = getEditableInternal(); |
| ExtractedText et = new ExtractedText(); |
| - et.text = editable.toString(); |
| - et.partialEndOffset = editable.length(); |
| - et.selectionStart = Selection.getSelectionStart(editable); |
| - et.selectionEnd = Selection.getSelectionEnd(editable); |
| + et.text = mEditable.toString(); |
| + et.partialEndOffset = mEditable.length(); |
| + et.selectionStart = Selection.getSelectionStart(mEditable); |
| + et.selectionEnd = Selection.getSelectionEnd(mEditable); |
| et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; |
| return et; |
| } |
| @@ -387,20 +333,19 @@ public class AdapterInputConnection extends BaseInputConnection { |
| finishComposingText(); |
| } |
| - Editable editable = getEditableInternal(); |
| - int selectionStart = Selection.getSelectionStart(editable); |
| - int selectionEnd = Selection.getSelectionEnd(editable); |
| + int selectionStart = Selection.getSelectionStart(mEditable); |
| + int selectionEnd = Selection.getSelectionEnd(mEditable); |
| int availableBefore = selectionStart; |
| - int availableAfter = editable.length() - selectionEnd; |
| + int availableAfter = mEditable.length() - selectionEnd; |
| beforeLength = Math.min(beforeLength, availableBefore); |
| afterLength = Math.min(afterLength, availableAfter); |
| // Adjust these values even before calling super.deleteSurroundingText() to be consistent |
| // with the super class. |
| - if (isIndexBetweenUtf16SurrogatePair(editable, selectionStart - beforeLength)) { |
| + if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionStart - beforeLength)) { |
| beforeLength += 1; |
| } |
| - if (isIndexBetweenUtf16SurrogatePair(editable, selectionEnd + afterLength)) { |
| + if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionEnd + afterLength)) { |
| afterLength += 1; |
| } |
| @@ -417,6 +362,14 @@ public class AdapterInputConnection extends BaseInputConnection { |
| } |
| /** |
| + * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent) |
| + */ |
| + @Override |
| + public boolean sendKeyEventOnUiThread(KeyEvent event) { |
| + return sendKeyEvent(event); |
| + } |
| + |
| + /** |
| * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) |
| */ |
| @Override |
| @@ -480,15 +433,14 @@ public class AdapterInputConnection extends BaseInputConnection { |
| */ |
| private void replaceSelectionWithUnicodeChar(int unicodeChar) { |
| if (unicodeChar == 0) return; |
| - Editable editable = getEditableInternal(); |
| - int selectionStart = Selection.getSelectionStart(editable); |
| - int selectionEnd = Selection.getSelectionEnd(editable); |
| + int selectionStart = Selection.getSelectionStart(mEditable); |
| + int selectionEnd = Selection.getSelectionEnd(mEditable); |
| if (selectionStart > selectionEnd) { |
| int temp = selectionStart; |
| selectionStart = selectionEnd; |
| selectionEnd = temp; |
| } |
| - editable.replace(selectionStart, selectionEnd, Character.toString((char) unicodeChar)); |
| + mEditable.replace(selectionStart, selectionEnd, Character.toString((char) unicodeChar)); |
| updateSelectionIfRequired(); |
| } |
| @@ -500,8 +452,7 @@ public class AdapterInputConnection extends BaseInputConnection { |
| Log.d(TAG, "finishComposingText"); |
| mPendingAccent = 0; |
| - if (getComposingSpanStart(getEditableInternal()) |
| - == getComposingSpanEnd(getEditableInternal())) { |
| + if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) { |
| return true; |
| } |
| @@ -518,18 +469,16 @@ public class AdapterInputConnection extends BaseInputConnection { |
| @Override |
| public boolean setSelection(int start, int end) { |
| Log.d(TAG, "setSelection [%d %d]", start, end); |
| - int textLength = getEditableInternal().length(); |
| + int textLength = mEditable.length(); |
| if (start < 0 || end < 0 || start > textLength || end > textLength) return true; |
| super.setSelection(start, end); |
| updateSelectionIfRequired(); |
| return mImeAdapter.setEditableSelectionOffsets(start, end); |
| } |
| - /** |
| - * Call this when restartInput() is called. |
| - */ |
| - void onRestartInput() { |
| - Log.d(TAG, "onRestartInput"); |
| + @Override |
| + public void onRestartInputOnUiThread() { |
| + Log.d(TAG, "onRestartInputOnUiThread"); |
| mNumNestedBatchEdits = 0; |
| mPendingAccent = 0; |
| } |
| @@ -540,8 +489,7 @@ public class AdapterInputConnection extends BaseInputConnection { |
| @Override |
| public boolean setComposingRegion(int start, int end) { |
| Log.d(TAG, "setComposingRegion [%d %d]", start, end); |
| - Editable editable = getEditableInternal(); |
| - int textLength = editable.length(); |
| + int textLength = mEditable.length(); |
| int a = Math.min(start, end); |
| int b = Math.max(start, end); |
| if (a < 0) a = 0; |
| @@ -550,17 +498,24 @@ public class AdapterInputConnection extends BaseInputConnection { |
| if (b > textLength) b = textLength; |
| if (a == b) { |
| - removeComposingSpans(editable); |
| + removeComposingSpans(mEditable); |
| } else { |
| super.setComposingRegion(a, b); |
| } |
| updateSelectionIfRequired(); |
| + return mImeAdapter.setComposingRegion(a, b); |
| + } |
| - CharSequence regionText = null; |
| - if (b > a) { |
| - regionText = editable.subSequence(a, b); |
| - } |
| - return mImeAdapter.setComposingRegion(regionText, a, b); |
| + @Override |
| + public void moveCursorToSelectionEndOnUiThread() { |
| + Log.d(TAG, "movecursorToEnd"); |
| + int selectionEnd = Selection.getSelectionEnd(mEditable); |
| + setSelection(selectionEnd, selectionEnd); |
| + } |
| + |
| + @Override |
| + public ThreadManager getThreadManager() { |
| + return mThreadManager; |
| } |
| @VisibleForTesting |
| @@ -583,12 +538,11 @@ public class AdapterInputConnection extends BaseInputConnection { |
| @VisibleForTesting |
| ImeState getImeStateForTesting() { |
| - Editable editable = getEditableInternal(); |
| - String text = editable.toString(); |
| - int selectionStart = Selection.getSelectionStart(editable); |
| - int selectionEnd = Selection.getSelectionEnd(editable); |
| - int compositionStart = getComposingSpanStart(editable); |
| - int compositionEnd = getComposingSpanEnd(editable); |
| + String text = mEditable.toString(); |
| + int selectionStart = Selection.getSelectionStart(mEditable); |
| + int selectionEnd = Selection.getSelectionEnd(mEditable); |
| + int compositionStart = getComposingSpanStart(mEditable); |
| + int compositionEnd = getComposingSpanEnd(mEditable); |
| return new ImeState(text, selectionStart, selectionEnd, compositionStart, compositionEnd); |
| } |
| } |