Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.content.browser.input; | 5 package org.chromium.content.browser.input; |
| 6 | 6 |
| 7 import android.os.Handler; | |
| 8 import android.os.Looper; | |
| 7 import android.text.Editable; | 9 import android.text.Editable; |
| 8 import android.text.InputType; | |
| 9 import android.text.Selection; | 10 import android.text.Selection; |
| 10 import android.util.StringBuilderPrinter; | |
| 11 import android.view.KeyCharacterMap; | 11 import android.view.KeyCharacterMap; |
| 12 import android.view.KeyEvent; | 12 import android.view.KeyEvent; |
| 13 import android.view.View; | 13 import android.view.View; |
| 14 import android.view.inputmethod.BaseInputConnection; | 14 import android.view.inputmethod.BaseInputConnection; |
| 15 import android.view.inputmethod.EditorInfo; | 15 import android.view.inputmethod.EditorInfo; |
| 16 import android.view.inputmethod.ExtractedText; | 16 import android.view.inputmethod.ExtractedText; |
| 17 import android.view.inputmethod.ExtractedTextRequest; | 17 import android.view.inputmethod.ExtractedTextRequest; |
| 18 | 18 |
| 19 import org.chromium.base.Log; | 19 import org.chromium.base.Log; |
| 20 import org.chromium.base.VisibleForTesting; | 20 import org.chromium.base.VisibleForTesting; |
| 21 import org.chromium.blink_public.web.WebTextInputFlags; | |
| 22 import org.chromium.ui.base.ime.TextInputType; | |
| 23 | |
| 24 import java.util.Locale; | |
| 25 | 21 |
| 26 /** | 22 /** |
| 27 * InputConnection is created by ContentView.onCreateInputConnection. | 23 * InputConnection is created by ContentView.onCreateInputConnection. |
| 28 * It then adapts android's IME to chrome's RenderWidgetHostView using the | 24 * It then adapts android's IME to chrome's RenderWidgetHostView using the |
| 29 * native ImeAdapterAndroid via the class ImeAdapter. | 25 * native ImeAdapterAndroid via the class ImeAdapter. Replica refers to the loca l copy of |
| 26 * the textbox held in the Editable. | |
| 30 */ | 27 */ |
| 31 public class AdapterInputConnection extends BaseInputConnection { | 28 public class ReplicaInputConnection |
| 29 extends BaseInputConnection implements ChromiumBaseInputConnection { | |
| 32 private static final String TAG = "cr_Ime"; | 30 private static final String TAG = "cr_Ime"; |
| 33 private static final boolean DEBUG_LOGS = false; | 31 private static final boolean DEBUG_LOGS = false; |
| 34 /** | 32 /** |
| 35 * Selection value should be -1 if not known. See EditorInfo.java for detail s. | 33 * Selection value should be -1 if not known. See EditorInfo.java for detail s. |
| 36 */ | 34 */ |
| 37 public static final int INVALID_SELECTION = -1; | 35 public static final int INVALID_SELECTION = -1; |
| 36 | |
| 38 public static final int INVALID_COMPOSITION = -1; | 37 public static final int INVALID_COMPOSITION = -1; |
| 39 | 38 |
| 40 private final ImeAdapter mImeAdapter; | 39 private final ImeAdapter mImeAdapter; |
| 41 | 40 |
| 41 // This holds the state of editable text (e.g. contents of <input>, contente ditable) of | |
| 42 // a focused element. | |
| 43 // Every time the user, IME, javascript (Blink), autofill etc. modifies the content, the new | |
| 44 // state must be reflected to this to keep consistency. | |
| 45 private final Editable mEditable; | |
| 46 | |
| 42 private boolean mSingleLine; | 47 private boolean mSingleLine; |
| 43 private int mNumNestedBatchEdits = 0; | 48 private int mNumNestedBatchEdits = 0; |
| 44 private int mPendingAccent; | 49 private int mPendingAccent; |
| 50 private final Handler mHandler; | |
| 45 | 51 |
| 46 private int mLastUpdateSelectionStart = INVALID_SELECTION; | 52 /** |
| 47 private int mLastUpdateSelectionEnd = INVALID_SELECTION; | 53 * Default factory for AdapterInputConnection classes. |
| 48 private int mLastUpdateCompositionStart = INVALID_COMPOSITION; | 54 */ |
| 49 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; | 55 static class Factory implements ChromiumBaseInputConnection.Factory { |
| 56 // Note: we share Editable among input connections so that data remains the same on | |
| 57 // switching inputs. However, the downside is that initial value cannot be correct, and | |
| 58 // wrong value will be used when jumping from one input to another. | |
| 59 private final Editable mEditable; | |
| 50 | 60 |
| 51 @VisibleForTesting | 61 private final Handler mHandler; |
| 52 AdapterInputConnection(View view, ImeAdapter imeAdapter, int initialSelStart , int initialSelEnd, | |
| 53 EditorInfo outAttrs) { | |
| 54 super(view, true); | |
| 55 mImeAdapter = imeAdapter; | |
| 56 mImeAdapter.setInputConnection(this); | |
| 57 | 62 |
| 58 mSingleLine = true; | 63 Factory() { |
| 59 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | 64 mHandler = new Handler(Looper.getMainLooper()); |
| 60 | EditorInfo.IME_FLAG_NO_EXTRACT_UI; | 65 mEditable = Editable.Factory.getInstance().newEditable(""); |
| 61 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | 66 Selection.setSelection(mEditable, 0); |
| 62 | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; | |
| 63 | |
| 64 int inputType = imeAdapter.getTextInputType(); | |
| 65 int inputFlags = imeAdapter.getTextInputFlags(); | |
| 66 if ((inputFlags & WebTextInputFlags.AutocompleteOff) != 0) { | |
| 67 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS; | |
| 68 } | 67 } |
| 69 | 68 |
| 70 if (inputType == TextInputType.TEXT) { | 69 @Override |
| 71 // Normal text field | 70 public ReplicaInputConnection initializeAndGet(View view, ImeAdapter ime Adapter, |
| 72 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | 71 int inputType, int inputFlags, EditorInfo outAttrs) { |
| 73 if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { | 72 return new ReplicaInputConnection( |
| 74 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; | 73 view, imeAdapter, mHandler, mEditable, inputType, inputFlags , outAttrs); |
| 75 } | |
| 76 } else if (inputType == TextInputType.TEXT_AREA | |
| 77 || inputType == TextInputType.CONTENT_EDITABLE) { | |
| 78 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; | |
| 79 if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { | |
| 80 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; | |
| 81 } | |
| 82 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE; | |
| 83 mSingleLine = false; | |
| 84 } else if (inputType == TextInputType.PASSWORD) { | |
| 85 // Password | |
| 86 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | |
| 87 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; | |
| 88 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | |
| 89 } else if (inputType == TextInputType.SEARCH) { | |
| 90 // Search | |
| 91 outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH; | |
| 92 } else if (inputType == TextInputType.URL) { | |
| 93 // Url | |
| 94 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | |
| 95 | InputType.TYPE_TEXT_VARIATION_URI; | |
| 96 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | |
| 97 } else if (inputType == TextInputType.EMAIL) { | |
| 98 // Email | |
| 99 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | |
| 100 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; | |
| 101 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | |
| 102 } else if (inputType == TextInputType.TELEPHONE) { | |
| 103 // Telephone | |
| 104 // Number and telephone do not have both a Tab key and an | |
| 105 // action in default OSK, so set the action to NEXT | |
| 106 outAttrs.inputType = InputType.TYPE_CLASS_PHONE; | |
| 107 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; | |
| 108 } else if (inputType == TextInputType.NUMBER) { | |
| 109 // Number | |
| 110 outAttrs.inputType = InputType.TYPE_CLASS_NUMBER | |
| 111 | InputType.TYPE_NUMBER_VARIATION_NORMAL | |
| 112 | InputType.TYPE_NUMBER_FLAG_DECIMAL; | |
| 113 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; | |
| 114 } | 74 } |
| 115 | 75 |
| 116 // Handling of autocapitalize. Blink will send the flag taking into acco unt the element's | 76 @Override |
| 117 // type. This is not using AutocapitalizeNone because Android does not a utocapitalize by | 77 public Handler getHandler() { |
| 118 // default and there is no way to express no capitalization. | 78 return mHandler; |
| 119 // Autocapitalize is meant as a hint to the virtual keyboard. | |
| 120 if ((inputFlags & WebTextInputFlags.AutocapitalizeCharacters) != 0) { | |
| 121 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; | |
| 122 } else if ((inputFlags & WebTextInputFlags.AutocapitalizeWords) != 0) { | |
| 123 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS; | |
| 124 } else if ((inputFlags & WebTextInputFlags.AutocapitalizeSentences) != 0 ) { | |
| 125 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; | |
| 126 } | |
| 127 // Content editable doesn't use autocapitalize so we need to set it manu ally. | |
| 128 if (inputType == TextInputType.CONTENT_EDITABLE) { | |
| 129 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; | |
| 130 } | |
| 131 | |
| 132 outAttrs.initialSelStart = initialSelStart; | |
| 133 outAttrs.initialSelEnd = initialSelEnd; | |
| 134 mLastUpdateSelectionStart = outAttrs.initialSelStart; | |
| 135 mLastUpdateSelectionEnd = outAttrs.initialSelEnd; | |
| 136 if (DEBUG_LOGS) { | |
| 137 Log.w(TAG, "Constructor called with outAttrs: %s", dumpEditorInfo(ou tAttrs)); | |
| 138 } | 79 } |
| 139 } | 80 } |
| 140 | 81 |
| 141 private static String dumpEditorInfo(EditorInfo editorInfo) { | 82 @VisibleForTesting |
| 142 StringBuilder builder = new StringBuilder(); | 83 ReplicaInputConnection(View view, ImeAdapter imeAdapter, Handler handler, Ed itable editable, |
| 143 StringBuilderPrinter printer = new StringBuilderPrinter(builder); | 84 int inputType, int inputFlags, EditorInfo outAttrs) { |
| 144 editorInfo.dump(printer, ""); | 85 super(view, true); |
| 145 return builder.toString(); | 86 mImeAdapter = imeAdapter; |
| 87 mEditable = editable; | |
| 88 mHandler = handler; | |
| 89 | |
| 90 int initialSelStart = Selection.getSelectionStart(editable); | |
| 91 int initialSelEnd = Selection.getSelectionEnd(editable); | |
| 92 ImeUtils.computeEditorInfo(inputType, inputFlags, initialSelStart, initi alSelEnd, outAttrs); | |
| 93 | |
| 94 if (DEBUG_LOGS) { | |
| 95 Log.w(TAG, "Constructor called with outAttrs: %s", | |
| 96 ImeUtils.getEditorInfoDebugString(outAttrs)); | |
| 97 } | |
| 146 } | 98 } |
| 147 | 99 |
| 148 private static String dumpEditable(Editable editable) { | 100 @Override |
| 149 return String.format(Locale.US, "Editable {[%s] SEL[%d %d] COM[%d %d]}", | 101 public void updateStateOnUiThread(String text, int selectionStart, int selec tionEnd, |
| 150 editable.toString(), | 102 int compositionStart, int compositionEnd, boolean singleLine, boolea n isNonImeChange) { |
| 151 Selection.getSelectionStart(editable), | 103 Log.d(TAG, "updateState [%s] [%s %s] [%s %s] [%b] [%b]", text, selection Start, selectionEnd, |
| 152 Selection.getSelectionEnd(editable), | 104 compositionStart, compositionEnd, singleLine, isNonImeChange); |
| 153 getComposingSpanStart(editable), | 105 mSingleLine = singleLine; |
| 154 getComposingSpanEnd(editable)); | |
| 155 } | |
| 156 | 106 |
| 157 /** | |
| 158 * Updates the AdapterInputConnection's internal representation of the text being edited and | |
| 159 * its selection and composition properties. The resulting Editable is acces sible through the | |
| 160 * getEditable() method. If the text has not changed, this also calls update Selection on the | |
| 161 * InputMethodManager. | |
| 162 * | |
| 163 * @param text The String contents of the field being edited. | |
| 164 * @param selectionStart The character offset of the selection start, or the caret position if | |
| 165 * there is no selection. | |
| 166 * @param selectionEnd The character offset of the selection end, or the car et position if there | |
| 167 * is no selection. | |
| 168 * @param compositionStart The character offset of the composition start, or -1 if there is no | |
| 169 * composition. | |
| 170 * @param compositionEnd The character offset of the composition end, or -1 if there is no | |
| 171 * selection. | |
| 172 * @param isNonImeChange True when the update was caused by non-IME (e.g. Ja vascript). | |
| 173 */ | |
| 174 @VisibleForTesting | |
| 175 public void updateState(String text, int selectionStart, int selectionEnd, i nt compositionStart, | |
| 176 int compositionEnd, boolean isNonImeChange) { | |
| 177 if (DEBUG_LOGS) Log.w(TAG, "updateState [%s] [%s %s] [%s %s] [%b]", text , selectionStart, | |
| 178 selectionEnd, compositionStart, compositionEnd, isNonImeChange); | |
| 179 // If this update is from the IME, no further state modification is nece ssary because the | 107 // If this update is from the IME, no further state modification is nece ssary because the |
| 180 // state should have been updated already by the IM framework directly. | 108 // state should have been updated already by the IM framework directly. |
| 181 if (!isNonImeChange) return; | 109 if (!isNonImeChange) return; |
| 182 | 110 |
| 183 // Non-breaking spaces can cause the IME to get confused. Replace with n ormal spaces. | 111 // Non-breaking spaces can cause the IME to get confused. Replace with n ormal spaces. |
| 184 text = text.replace('\u00A0', ' '); | 112 text = text.replace('\u00A0', ' '); |
| 185 | 113 |
| 186 selectionStart = Math.min(selectionStart, text.length()); | 114 selectionStart = Math.min(selectionStart, text.length()); |
| 187 selectionEnd = Math.min(selectionEnd, text.length()); | 115 selectionEnd = Math.min(selectionEnd, text.length()); |
| 188 compositionStart = Math.min(compositionStart, text.length()); | 116 compositionStart = Math.min(compositionStart, text.length()); |
| 189 compositionEnd = Math.min(compositionEnd, text.length()); | 117 compositionEnd = Math.min(compositionEnd, text.length()); |
| 190 | 118 |
| 191 Editable editable = getEditableInternal(); | 119 String prevText = mEditable.toString(); |
| 192 | |
| 193 String prevText = editable.toString(); | |
| 194 boolean textUnchanged = prevText.equals(text); | 120 boolean textUnchanged = prevText.equals(text); |
| 195 | 121 |
| 196 if (!textUnchanged) { | 122 if (!textUnchanged) { |
| 197 editable.replace(0, editable.length(), text); | 123 mEditable.replace(0, mEditable.length(), text); |
| 198 } | 124 } |
| 199 | 125 |
| 200 Selection.setSelection(editable, selectionStart, selectionEnd); | 126 Selection.setSelection(mEditable, selectionStart, selectionEnd); |
| 201 | 127 |
| 202 if (compositionStart == compositionEnd) { | 128 if (compositionStart == compositionEnd) { |
| 203 removeComposingSpans(editable); | 129 removeComposingSpans(mEditable); |
| 204 } else { | 130 } else { |
| 205 super.setComposingRegion(compositionStart, compositionEnd); | 131 super.setComposingRegion(compositionStart, compositionEnd); |
| 206 } | 132 } |
| 207 updateSelectionIfRequired(); | 133 updateSelectionIfRequired(); |
| 208 } | 134 } |
| 209 | 135 |
| 210 /** | 136 /** |
| 211 * @see BaseInputConnection#getEditable() | 137 * @see BaseInputConnection#getEditable() |
| 212 */ | 138 */ |
| 213 @Override | 139 @Override |
| 214 public Editable getEditable() { | 140 public Editable getEditable() { |
| 215 Editable editable = getEditableInternal(); | 141 if (DEBUG_LOGS) Log.w(TAG, "getEditable: %s", ImeUtils.getEditableDebugS tring(mEditable)); |
| 216 if (DEBUG_LOGS) Log.w(TAG, "getEditable: %s", dumpEditable(editable)); | 142 return mEditable; |
| 217 return editable; | |
| 218 } | |
| 219 | |
| 220 private Editable getEditableInternal() { | |
| 221 return mImeAdapter.getEditable(); | |
| 222 } | 143 } |
| 223 | 144 |
| 224 /** | 145 /** |
| 225 * Sends selection update to the InputMethodManager unless we are currently in a batch edit or | 146 * Sends selection update to the InputMethodManager unless we are currently in a batch edit or |
| 226 * if the exact same selection and composition update was sent already. | 147 * if the exact same selection and composition update was sent already. |
| 227 */ | 148 */ |
| 228 private void updateSelectionIfRequired() { | 149 private void updateSelectionIfRequired() { |
| 229 if (mNumNestedBatchEdits != 0) return; | 150 if (mNumNestedBatchEdits != 0) return; |
| 230 Editable editable = getEditableInternal(); | 151 int selectionStart = Selection.getSelectionStart(mEditable); |
| 231 int selectionStart = Selection.getSelectionStart(editable); | 152 int selectionEnd = Selection.getSelectionEnd(mEditable); |
| 232 int selectionEnd = Selection.getSelectionEnd(editable); | 153 int compositionStart = getComposingSpanStart(mEditable); |
| 233 int compositionStart = getComposingSpanStart(editable); | 154 int compositionEnd = getComposingSpanEnd(mEditable); |
| 234 int compositionEnd = getComposingSpanEnd(editable); | |
| 235 // Avoid sending update if we sent an exact update already previously. | 155 // Avoid sending update if we sent an exact update already previously. |
|
Ted C
2016/02/17 19:09:33
we are now sending it always right? Is that inten
Changwan Ryu
2016/02/18 06:03:26
Removed the comment.
The same check is already th
| |
| 236 if (mLastUpdateSelectionStart == selectionStart | 156 if (DEBUG_LOGS) { |
| 237 && mLastUpdateSelectionEnd == selectionEnd | 157 Log.w(TAG, "updateSelectionIfRequired [%d %d] [%d %d]", selectionSta rt, selectionEnd, |
| 238 && mLastUpdateCompositionStart == compositionStart | 158 compositionStart, compositionEnd); |
| 239 && mLastUpdateCompositionEnd == compositionEnd) { | |
| 240 return; | |
| 241 } | 159 } |
| 242 if (DEBUG_LOGS) Log.w(TAG, "updateSelectionIfRequired [%d %d] [%d %d]", selectionStart, | |
| 243 selectionEnd, compositionStart, compositionEnd); | |
| 244 // updateSelection should be called every time the selection or composit ion changes | 160 // updateSelection should be called every time the selection or composit ion changes |
| 245 // if it happens not within a batch edit, or at the end of each top leve l batch edit. | 161 // if it happens not within a batch edit, or at the end of each top leve l batch edit. |
| 246 mImeAdapter.updateSelection(selectionStart, selectionEnd, compositionSta rt, compositionEnd); | 162 mImeAdapter.updateSelection(selectionStart, selectionEnd, compositionSta rt, compositionEnd); |
| 247 mLastUpdateSelectionStart = selectionStart; | |
| 248 mLastUpdateSelectionEnd = selectionEnd; | |
| 249 mLastUpdateCompositionStart = compositionStart; | |
| 250 mLastUpdateCompositionEnd = compositionEnd; | |
| 251 } | 163 } |
| 252 | 164 |
| 253 /** | 165 /** |
| 254 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) | 166 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) |
| 255 */ | 167 */ |
| 256 @Override | 168 @Override |
| 257 public boolean setComposingText(CharSequence text, int newCursorPosition) { | 169 public boolean setComposingText(CharSequence text, int newCursorPosition) { |
| 258 if (DEBUG_LOGS) Log.w(TAG, "setComposingText [%s] [%d]", text, newCursor Position); | 170 if (DEBUG_LOGS) Log.w(TAG, "setComposingText [%s] [%d]", text, newCursor Position); |
| 259 mPendingAccent = 0; | 171 mPendingAccent = 0; |
| 260 super.setComposingText(text, newCursorPosition); | 172 super.setComposingText(text, newCursorPosition); |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 292 return mImeAdapter.performContextMenuAction(id); | 204 return mImeAdapter.performContextMenuAction(id); |
| 293 } | 205 } |
| 294 | 206 |
| 295 /** | 207 /** |
| 296 * @see BaseInputConnection#getExtractedText(android.view.inputmethod.Extrac tedTextRequest, | 208 * @see BaseInputConnection#getExtractedText(android.view.inputmethod.Extrac tedTextRequest, |
| 297 * int) | 209 * int) |
| 298 */ | 210 */ |
| 299 @Override | 211 @Override |
| 300 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag s) { | 212 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag s) { |
| 301 if (DEBUG_LOGS) Log.w(TAG, "getExtractedText"); | 213 if (DEBUG_LOGS) Log.w(TAG, "getExtractedText"); |
| 302 Editable editable = getEditableInternal(); | |
| 303 ExtractedText et = new ExtractedText(); | 214 ExtractedText et = new ExtractedText(); |
| 304 et.text = editable.toString(); | 215 et.text = mEditable.toString(); |
| 305 et.partialEndOffset = editable.length(); | 216 et.partialEndOffset = mEditable.length(); |
| 306 et.selectionStart = Selection.getSelectionStart(editable); | 217 et.selectionStart = Selection.getSelectionStart(mEditable); |
| 307 et.selectionEnd = Selection.getSelectionEnd(editable); | 218 et.selectionEnd = Selection.getSelectionEnd(mEditable); |
| 308 et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; | 219 et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; |
| 309 return et; | 220 return et; |
| 310 } | 221 } |
| 311 | 222 |
| 312 /** | 223 /** |
| 313 * @see BaseInputConnection#beginBatchEdit() | 224 * @see BaseInputConnection#beginBatchEdit() |
| 314 */ | 225 */ |
| 315 @Override | 226 @Override |
| 316 public boolean beginBatchEdit() { | 227 public boolean beginBatchEdit() { |
| 317 if (DEBUG_LOGS) Log.w(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0)); | 228 if (DEBUG_LOGS) Log.w(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0)); |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 355 int beforeLength, int afterLength, boolean fromPhysicalKey) { | 266 int beforeLength, int afterLength, boolean fromPhysicalKey) { |
| 356 if (DEBUG_LOGS) { | 267 if (DEBUG_LOGS) { |
| 357 Log.w(TAG, "deleteSurroundingText [%d %d %b]", beforeLength, afterLe ngth, | 268 Log.w(TAG, "deleteSurroundingText [%d %d %b]", beforeLength, afterLe ngth, |
| 358 fromPhysicalKey); | 269 fromPhysicalKey); |
| 359 } | 270 } |
| 360 | 271 |
| 361 if (mPendingAccent != 0) { | 272 if (mPendingAccent != 0) { |
| 362 finishComposingText(); | 273 finishComposingText(); |
| 363 } | 274 } |
| 364 | 275 |
| 365 Editable editable = getEditableInternal(); | 276 int selectionStart = Selection.getSelectionStart(mEditable); |
| 366 int selectionStart = Selection.getSelectionStart(editable); | 277 int selectionEnd = Selection.getSelectionEnd(mEditable); |
| 367 int selectionEnd = Selection.getSelectionEnd(editable); | |
| 368 int availableBefore = selectionStart; | 278 int availableBefore = selectionStart; |
| 369 int availableAfter = editable.length() - selectionEnd; | 279 int availableAfter = mEditable.length() - selectionEnd; |
| 370 beforeLength = Math.min(beforeLength, availableBefore); | 280 beforeLength = Math.min(beforeLength, availableBefore); |
| 371 afterLength = Math.min(afterLength, availableAfter); | 281 afterLength = Math.min(afterLength, availableAfter); |
| 372 | 282 |
| 373 // Adjust these values even before calling super.deleteSurroundingText() to be consistent | 283 // Adjust these values even before calling super.deleteSurroundingText() to be consistent |
| 374 // with the super class. | 284 // with the super class. |
| 375 if (isIndexBetweenUtf16SurrogatePair(editable, selectionStart - beforeLe ngth)) { | 285 if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionStart - beforeL ength)) { |
| 376 beforeLength += 1; | 286 beforeLength += 1; |
| 377 } | 287 } |
| 378 if (isIndexBetweenUtf16SurrogatePair(editable, selectionEnd + afterLengt h)) { | 288 if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionEnd + afterLeng th)) { |
| 379 afterLength += 1; | 289 afterLength += 1; |
| 380 } | 290 } |
| 381 | 291 |
| 382 super.deleteSurroundingText(beforeLength, afterLength); | 292 super.deleteSurroundingText(beforeLength, afterLength); |
| 383 updateSelectionIfRequired(); | 293 updateSelectionIfRequired(); |
| 384 | 294 |
| 385 // If this was called due to a physical key, no need to generate a key e vent here as | 295 // If this was called due to a physical key, no need to generate a key e vent here as |
| 386 // the caller will take care of forwarding the original. | 296 // the caller will take care of forwarding the original. |
| 387 if (fromPhysicalKey) { | 297 if (fromPhysicalKey) { |
| 388 return true; | 298 return true; |
| 389 } | 299 } |
| 390 | 300 |
| 391 return mImeAdapter.deleteSurroundingText(beforeLength, afterLength); | 301 return mImeAdapter.deleteSurroundingText(beforeLength, afterLength); |
| 392 } | 302 } |
| 393 | 303 |
| 394 /** | 304 /** |
| 305 * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent) | |
| 306 */ | |
| 307 @Override | |
| 308 public boolean sendKeyEventOnUiThread(KeyEvent event) { | |
| 309 return sendKeyEvent(event); | |
| 310 } | |
| 311 | |
| 312 /** | |
| 395 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) | 313 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) |
| 396 */ | 314 */ |
| 397 @Override | 315 @Override |
| 398 public boolean sendKeyEvent(KeyEvent event) { | 316 public boolean sendKeyEvent(KeyEvent event) { |
| 399 if (DEBUG_LOGS) { | 317 if (DEBUG_LOGS) { |
| 400 Log.w(TAG, "sendKeyEvent [%d] [%d] [%d]", event.getAction(), event.g etKeyCode(), | 318 Log.w(TAG, "sendKeyEvent [%d] [%d] [%d]", event.getAction(), event.g etKeyCode(), |
| 401 event.getUnicodeChar()); | 319 event.getUnicodeChar()); |
| 402 } | 320 } |
| 403 | 321 |
| 404 int action = event.getAction(); | 322 int action = event.getAction(); |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 450 return true; | 368 return true; |
| 451 } | 369 } |
| 452 | 370 |
| 453 /** | 371 /** |
| 454 * Update the Editable to reflect what Blink will do in response to the KeyD own for a | 372 * Update the Editable to reflect what Blink will do in response to the KeyD own for a |
| 455 * unicode-mapped key event. | 373 * unicode-mapped key event. |
| 456 * @param unicodeChar The Unicode character to update selection with. | 374 * @param unicodeChar The Unicode character to update selection with. |
| 457 */ | 375 */ |
| 458 private void replaceSelectionWithUnicodeChar(int unicodeChar) { | 376 private void replaceSelectionWithUnicodeChar(int unicodeChar) { |
| 459 if (unicodeChar == 0) return; | 377 if (unicodeChar == 0) return; |
| 460 Editable editable = getEditableInternal(); | 378 int selectionStart = Selection.getSelectionStart(mEditable); |
| 461 int selectionStart = Selection.getSelectionStart(editable); | 379 int selectionEnd = Selection.getSelectionEnd(mEditable); |
| 462 int selectionEnd = Selection.getSelectionEnd(editable); | |
| 463 if (selectionStart > selectionEnd) { | 380 if (selectionStart > selectionEnd) { |
| 464 int temp = selectionStart; | 381 int temp = selectionStart; |
| 465 selectionStart = selectionEnd; | 382 selectionStart = selectionEnd; |
| 466 selectionEnd = temp; | 383 selectionEnd = temp; |
| 467 } | 384 } |
| 468 editable.replace(selectionStart, selectionEnd, Character.toString((char) unicodeChar)); | 385 mEditable.replace(selectionStart, selectionEnd, Character.toString((char ) unicodeChar)); |
| 469 updateSelectionIfRequired(); | 386 updateSelectionIfRequired(); |
| 470 } | 387 } |
| 471 | 388 |
| 472 /** | 389 /** |
| 473 * @see BaseInputConnection#finishComposingText() | 390 * @see BaseInputConnection#finishComposingText() |
| 474 */ | 391 */ |
| 475 @Override | 392 @Override |
| 476 public boolean finishComposingText() { | 393 public boolean finishComposingText() { |
| 477 if (DEBUG_LOGS) Log.w(TAG, "finishComposingText"); | 394 if (DEBUG_LOGS) Log.w(TAG, "finishComposingText"); |
| 478 mPendingAccent = 0; | 395 mPendingAccent = 0; |
| 479 | 396 |
| 480 if (getComposingSpanStart(getEditableInternal()) | 397 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) { |
| 481 == getComposingSpanEnd(getEditableInternal())) { | |
| 482 return true; | 398 return true; |
| 483 } | 399 } |
| 484 | 400 |
| 485 super.finishComposingText(); | 401 super.finishComposingText(); |
| 486 updateSelectionIfRequired(); | 402 updateSelectionIfRequired(); |
| 487 mImeAdapter.finishComposingText(); | 403 mImeAdapter.finishComposingText(); |
| 488 | 404 |
| 489 return true; | 405 return true; |
| 490 } | 406 } |
| 491 | 407 |
| 492 /** | 408 /** |
| 493 * @see BaseInputConnection#setSelection(int, int) | 409 * @see BaseInputConnection#setSelection(int, int) |
| 494 */ | 410 */ |
| 495 @Override | 411 @Override |
| 496 public boolean setSelection(int start, int end) { | 412 public boolean setSelection(int start, int end) { |
| 497 if (DEBUG_LOGS) Log.w(TAG, "setSelection [%d %d]", start, end); | 413 if (DEBUG_LOGS) Log.w(TAG, "setSelection [%d %d]", start, end); |
| 498 int textLength = getEditableInternal().length(); | 414 int textLength = mEditable.length(); |
| 499 if (start < 0 || end < 0 || start > textLength || end > textLength) retu rn true; | 415 if (start < 0 || end < 0 || start > textLength || end > textLength) retu rn true; |
| 500 super.setSelection(start, end); | 416 super.setSelection(start, end); |
| 501 updateSelectionIfRequired(); | 417 updateSelectionIfRequired(); |
| 502 return mImeAdapter.setEditableSelectionOffsets(start, end); | 418 return mImeAdapter.setEditableSelectionOffsets(start, end); |
| 503 } | 419 } |
| 504 | 420 |
| 505 /** | 421 /** |
| 506 * Call this when restartInput() is called. | |
| 507 */ | |
| 508 void onRestartInput() { | |
| 509 if (DEBUG_LOGS) Log.w(TAG, "onRestartInput"); | |
| 510 mNumNestedBatchEdits = 0; | |
| 511 mPendingAccent = 0; | |
| 512 } | |
| 513 | |
| 514 /** | |
| 515 * @see BaseInputConnection#setComposingRegion(int, int) | 422 * @see BaseInputConnection#setComposingRegion(int, int) |
| 516 */ | 423 */ |
| 517 @Override | 424 @Override |
| 518 public boolean setComposingRegion(int start, int end) { | 425 public boolean setComposingRegion(int start, int end) { |
| 519 if (DEBUG_LOGS) Log.w(TAG, "setComposingRegion [%d %d]", start, end); | 426 if (DEBUG_LOGS) Log.w(TAG, "setComposingRegion [%d %d]", start, end); |
| 520 Editable editable = getEditableInternal(); | 427 int textLength = mEditable.length(); |
| 521 int textLength = editable.length(); | |
| 522 int a = Math.min(start, end); | 428 int a = Math.min(start, end); |
| 523 int b = Math.max(start, end); | 429 int b = Math.max(start, end); |
| 524 if (a < 0) a = 0; | 430 if (a < 0) a = 0; |
| 525 if (b < 0) b = 0; | 431 if (b < 0) b = 0; |
| 526 if (a > textLength) a = textLength; | 432 if (a > textLength) a = textLength; |
| 527 if (b > textLength) b = textLength; | 433 if (b > textLength) b = textLength; |
| 528 | 434 |
| 529 if (a == b) { | 435 if (a == b) { |
| 530 removeComposingSpans(editable); | 436 removeComposingSpans(mEditable); |
| 531 } else { | 437 } else { |
| 532 super.setComposingRegion(a, b); | 438 super.setComposingRegion(a, b); |
| 533 } | 439 } |
| 534 updateSelectionIfRequired(); | 440 updateSelectionIfRequired(); |
| 441 return mImeAdapter.setComposingRegion(a, b); | |
| 442 } | |
| 535 | 443 |
| 536 CharSequence regionText = null; | 444 @Override |
| 537 if (b > a) { | 445 public void onRestartInputOnUiThread() { |
| 538 regionText = editable.subSequence(a, b); | 446 Log.d(TAG, "onRestartInputOnUiThread"); |
|
Ted C
2016/02/17 19:09:33
DEBUG_LOGS guard?
Changwan Ryu
2016/02/18 06:03:26
Done.
| |
| 539 } | 447 mNumNestedBatchEdits = 0; |
| 540 return mImeAdapter.setComposingRegion(regionText, a, b); | 448 mPendingAccent = 0; |
| 449 } | |
| 450 | |
| 451 @Override | |
| 452 public void moveCursorToSelectionEndOnUiThread() { | |
| 453 if (DEBUG_LOGS) Log.w(TAG, "movecursorToEnd"); | |
| 454 int selectionEnd = Selection.getSelectionEnd(mEditable); | |
| 455 setSelection(selectionEnd, selectionEnd); | |
| 456 } | |
| 457 | |
| 458 @Override | |
| 459 public void unblockOnUiThread() {} | |
| 460 | |
| 461 @Override | |
| 462 public Handler getHandler() { | |
| 463 return mHandler; | |
| 541 } | 464 } |
| 542 | 465 |
| 543 @VisibleForTesting | 466 @VisibleForTesting |
| 544 static class ImeState { | 467 static class ImeState { |
| 545 public final String text; | 468 public final String text; |
| 546 public final int selectionStart; | 469 public final int selectionStart; |
| 547 public final int selectionEnd; | 470 public final int selectionEnd; |
| 548 public final int compositionStart; | 471 public final int compositionStart; |
| 549 public final int compositionEnd; | 472 public final int compositionEnd; |
| 550 | 473 |
| 551 public ImeState(String text, int selectionStart, int selectionEnd, | 474 public ImeState(String text, int selectionStart, int selectionEnd, int c ompositionStart, |
| 552 int compositionStart, int compositionEnd) { | 475 int compositionEnd) { |
| 553 this.text = text; | 476 this.text = text; |
| 554 this.selectionStart = selectionStart; | 477 this.selectionStart = selectionStart; |
| 555 this.selectionEnd = selectionEnd; | 478 this.selectionEnd = selectionEnd; |
| 556 this.compositionStart = compositionStart; | 479 this.compositionStart = compositionStart; |
| 557 this.compositionEnd = compositionEnd; | 480 this.compositionEnd = compositionEnd; |
| 558 } | 481 } |
| 559 } | 482 } |
| 560 | 483 |
| 561 @VisibleForTesting | 484 @VisibleForTesting |
| 562 ImeState getImeStateForTesting() { | 485 ImeState getImeStateForTesting() { |
| 563 Editable editable = getEditableInternal(); | 486 String text = mEditable.toString(); |
| 564 String text = editable.toString(); | 487 int selectionStart = Selection.getSelectionStart(mEditable); |
| 565 int selectionStart = Selection.getSelectionStart(editable); | 488 int selectionEnd = Selection.getSelectionEnd(mEditable); |
| 566 int selectionEnd = Selection.getSelectionEnd(editable); | 489 int compositionStart = getComposingSpanStart(mEditable); |
| 567 int compositionStart = getComposingSpanStart(editable); | 490 int compositionEnd = getComposingSpanEnd(mEditable); |
| 568 int compositionEnd = getComposingSpanEnd(editable); | |
| 569 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd); | 491 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd); |
| 570 } | 492 } |
| 571 } | 493 } |
| OLD | NEW |