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, int selectionStart, int selection
End, |
73 if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) { | 72 EditorInfo outAttrs) { |
74 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; | 73 new InputMethodUma().recordProxyViewReplicaInputConnection(); |
75 } | 74 return new ReplicaInputConnection( |
76 } else if (inputType == TextInputType.TEXT_AREA | 75 view, imeAdapter, mHandler, mEditable, inputType, inputFlags
, outAttrs); |
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 } | 76 } |
115 | 77 |
116 // Handling of autocapitalize. Blink will send the flag taking into acco
unt the element's | 78 @Override |
117 // type. This is not using AutocapitalizeNone because Android does not a
utocapitalize by | 79 public Handler getHandler() { |
118 // default and there is no way to express no capitalization. | 80 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 } | 81 } |
139 } | 82 } |
140 | 83 |
141 private static String dumpEditorInfo(EditorInfo editorInfo) { | 84 @VisibleForTesting |
142 StringBuilder builder = new StringBuilder(); | 85 ReplicaInputConnection(View view, ImeAdapter imeAdapter, Handler handler, Ed
itable editable, |
143 StringBuilderPrinter printer = new StringBuilderPrinter(builder); | 86 int inputType, int inputFlags, EditorInfo outAttrs) { |
144 editorInfo.dump(printer, ""); | 87 super(view, true); |
145 return builder.toString(); | 88 mImeAdapter = imeAdapter; |
| 89 mEditable = editable; |
| 90 mHandler = handler; |
| 91 |
| 92 int initialSelStart = Selection.getSelectionStart(editable); |
| 93 int initialSelEnd = Selection.getSelectionEnd(editable); |
| 94 ImeUtils.computeEditorInfo(inputType, inputFlags, initialSelStart, initi
alSelEnd, outAttrs); |
| 95 |
| 96 if (DEBUG_LOGS) { |
| 97 Log.w(TAG, "Constructor called with outAttrs: %s", |
| 98 ImeUtils.getEditorInfoDebugString(outAttrs)); |
| 99 } |
146 } | 100 } |
147 | 101 |
148 private static String dumpEditable(Editable editable) { | 102 @Override |
149 return String.format(Locale.US, "Editable {[%s] SEL[%d %d] COM[%d %d]}", | 103 public void updateStateOnUiThread(String text, int selectionStart, int selec
tionEnd, |
150 editable.toString(), | 104 int compositionStart, int compositionEnd, boolean singleLine, boolea
n isNonImeChange) { |
151 Selection.getSelectionStart(editable), | 105 if (DEBUG_LOGS) { |
152 Selection.getSelectionEnd(editable), | 106 Log.w(TAG, "updateState [%s] [%s %s] [%s %s] [%b] [%b]", text, selec
tionStart, |
153 getComposingSpanStart(editable), | 107 selectionEnd, compositionStart, compositionEnd, singleLine,
isNonImeChange); |
154 getComposingSpanEnd(editable)); | 108 } |
155 } | 109 mSingleLine = singleLine; |
156 | 110 |
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 | 111 // 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. | 112 // state should have been updated already by the IM framework directly. |
181 if (!isNonImeChange) return; | 113 if (!isNonImeChange) return; |
182 | 114 |
183 // Non-breaking spaces can cause the IME to get confused. Replace with n
ormal spaces. | 115 // Non-breaking spaces can cause the IME to get confused. Replace with n
ormal spaces. |
184 text = text.replace('\u00A0', ' '); | 116 text = text.replace('\u00A0', ' '); |
185 | 117 |
186 selectionStart = Math.min(selectionStart, text.length()); | 118 selectionStart = Math.min(selectionStart, text.length()); |
187 selectionEnd = Math.min(selectionEnd, text.length()); | 119 selectionEnd = Math.min(selectionEnd, text.length()); |
188 compositionStart = Math.min(compositionStart, text.length()); | 120 compositionStart = Math.min(compositionStart, text.length()); |
189 compositionEnd = Math.min(compositionEnd, text.length()); | 121 compositionEnd = Math.min(compositionEnd, text.length()); |
190 | 122 |
191 Editable editable = getEditableInternal(); | 123 String prevText = mEditable.toString(); |
192 | |
193 String prevText = editable.toString(); | |
194 boolean textUnchanged = prevText.equals(text); | 124 boolean textUnchanged = prevText.equals(text); |
195 | 125 |
196 if (!textUnchanged) { | 126 if (!textUnchanged) { |
197 editable.replace(0, editable.length(), text); | 127 mEditable.replace(0, mEditable.length(), text); |
198 } | 128 } |
199 | 129 |
200 Selection.setSelection(editable, selectionStart, selectionEnd); | 130 Selection.setSelection(mEditable, selectionStart, selectionEnd); |
201 | 131 |
202 if (compositionStart == compositionEnd) { | 132 if (compositionStart == compositionEnd) { |
203 removeComposingSpans(editable); | 133 removeComposingSpans(mEditable); |
204 } else { | 134 } else { |
205 super.setComposingRegion(compositionStart, compositionEnd); | 135 super.setComposingRegion(compositionStart, compositionEnd); |
206 } | 136 } |
207 updateSelectionIfRequired(); | 137 updateSelectionIfRequired(); |
208 } | 138 } |
209 | 139 |
210 /** | 140 /** |
211 * @see BaseInputConnection#getEditable() | 141 * @see BaseInputConnection#getEditable() |
212 */ | 142 */ |
213 @Override | 143 @Override |
214 public Editable getEditable() { | 144 public Editable getEditable() { |
215 Editable editable = getEditableInternal(); | 145 if (DEBUG_LOGS) Log.w(TAG, "getEditable: %s", ImeUtils.getEditableDebugS
tring(mEditable)); |
216 if (DEBUG_LOGS) Log.w(TAG, "getEditable: %s", dumpEditable(editable)); | 146 return mEditable; |
217 return editable; | |
218 } | |
219 | |
220 private Editable getEditableInternal() { | |
221 return mImeAdapter.getEditable(); | |
222 } | 147 } |
223 | 148 |
224 /** | 149 /** |
225 * Sends selection update to the InputMethodManager unless we are currently
in a batch edit or | 150 * 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. | 151 * if the exact same selection and composition update was sent already. |
227 */ | 152 */ |
228 private void updateSelectionIfRequired() { | 153 private void updateSelectionIfRequired() { |
229 if (mNumNestedBatchEdits != 0) return; | 154 if (mNumNestedBatchEdits != 0) return; |
230 Editable editable = getEditableInternal(); | 155 int selectionStart = Selection.getSelectionStart(mEditable); |
231 int selectionStart = Selection.getSelectionStart(editable); | 156 int selectionEnd = Selection.getSelectionEnd(mEditable); |
232 int selectionEnd = Selection.getSelectionEnd(editable); | 157 int compositionStart = getComposingSpanStart(mEditable); |
233 int compositionStart = getComposingSpanStart(editable); | 158 int compositionEnd = getComposingSpanEnd(mEditable); |
234 int compositionEnd = getComposingSpanEnd(editable); | 159 if (DEBUG_LOGS) { |
235 // Avoid sending update if we sent an exact update already previously. | 160 Log.w(TAG, "updateSelectionIfRequired [%d %d] [%d %d]", selectionSta
rt, selectionEnd, |
236 if (mLastUpdateSelectionStart == selectionStart | 161 compositionStart, compositionEnd); |
237 && mLastUpdateSelectionEnd == selectionEnd | |
238 && mLastUpdateCompositionStart == compositionStart | |
239 && mLastUpdateCompositionEnd == compositionEnd) { | |
240 return; | |
241 } | 162 } |
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 | 163 // 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. | 164 // 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); | 165 mImeAdapter.updateSelection(selectionStart, selectionEnd, compositionSta
rt, compositionEnd); |
247 mLastUpdateSelectionStart = selectionStart; | |
248 mLastUpdateSelectionEnd = selectionEnd; | |
249 mLastUpdateCompositionStart = compositionStart; | |
250 mLastUpdateCompositionEnd = compositionEnd; | |
251 } | 166 } |
252 | 167 |
253 /** | 168 /** |
254 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) | 169 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) |
255 */ | 170 */ |
256 @Override | 171 @Override |
257 public boolean setComposingText(CharSequence text, int newCursorPosition) { | 172 public boolean setComposingText(CharSequence text, int newCursorPosition) { |
258 if (DEBUG_LOGS) Log.w(TAG, "setComposingText [%s] [%d]", text, newCursor
Position); | 173 if (DEBUG_LOGS) Log.w(TAG, "setComposingText [%s] [%d]", text, newCursor
Position); |
259 mPendingAccent = 0; | 174 mPendingAccent = 0; |
260 super.setComposingText(text, newCursorPosition); | 175 super.setComposingText(text, newCursorPosition); |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
292 return mImeAdapter.performContextMenuAction(id); | 207 return mImeAdapter.performContextMenuAction(id); |
293 } | 208 } |
294 | 209 |
295 /** | 210 /** |
296 * @see BaseInputConnection#getExtractedText(android.view.inputmethod.Extrac
tedTextRequest, | 211 * @see BaseInputConnection#getExtractedText(android.view.inputmethod.Extrac
tedTextRequest, |
297 * int) | 212 * int) |
298 */ | 213 */ |
299 @Override | 214 @Override |
300 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag
s) { | 215 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag
s) { |
301 if (DEBUG_LOGS) Log.w(TAG, "getExtractedText"); | 216 if (DEBUG_LOGS) Log.w(TAG, "getExtractedText"); |
302 Editable editable = getEditableInternal(); | |
303 ExtractedText et = new ExtractedText(); | 217 ExtractedText et = new ExtractedText(); |
304 et.text = editable.toString(); | 218 et.text = mEditable.toString(); |
305 et.partialEndOffset = editable.length(); | 219 et.partialEndOffset = mEditable.length(); |
306 et.selectionStart = Selection.getSelectionStart(editable); | 220 et.selectionStart = Selection.getSelectionStart(mEditable); |
307 et.selectionEnd = Selection.getSelectionEnd(editable); | 221 et.selectionEnd = Selection.getSelectionEnd(mEditable); |
308 et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; | 222 et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; |
309 return et; | 223 return et; |
310 } | 224 } |
311 | 225 |
312 /** | 226 /** |
313 * @see BaseInputConnection#beginBatchEdit() | 227 * @see BaseInputConnection#beginBatchEdit() |
314 */ | 228 */ |
315 @Override | 229 @Override |
316 public boolean beginBatchEdit() { | 230 public boolean beginBatchEdit() { |
317 if (DEBUG_LOGS) Log.w(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits
== 0)); | 231 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) { | 269 int beforeLength, int afterLength, boolean fromPhysicalKey) { |
356 if (DEBUG_LOGS) { | 270 if (DEBUG_LOGS) { |
357 Log.w(TAG, "deleteSurroundingText [%d %d %b]", beforeLength, afterLe
ngth, | 271 Log.w(TAG, "deleteSurroundingText [%d %d %b]", beforeLength, afterLe
ngth, |
358 fromPhysicalKey); | 272 fromPhysicalKey); |
359 } | 273 } |
360 | 274 |
361 if (mPendingAccent != 0) { | 275 if (mPendingAccent != 0) { |
362 finishComposingText(); | 276 finishComposingText(); |
363 } | 277 } |
364 | 278 |
365 Editable editable = getEditableInternal(); | 279 int selectionStart = Selection.getSelectionStart(mEditable); |
366 int selectionStart = Selection.getSelectionStart(editable); | 280 int selectionEnd = Selection.getSelectionEnd(mEditable); |
367 int selectionEnd = Selection.getSelectionEnd(editable); | |
368 int availableBefore = selectionStart; | 281 int availableBefore = selectionStart; |
369 int availableAfter = editable.length() - selectionEnd; | 282 int availableAfter = mEditable.length() - selectionEnd; |
370 beforeLength = Math.min(beforeLength, availableBefore); | 283 beforeLength = Math.min(beforeLength, availableBefore); |
371 afterLength = Math.min(afterLength, availableAfter); | 284 afterLength = Math.min(afterLength, availableAfter); |
372 | 285 |
373 // Adjust these values even before calling super.deleteSurroundingText()
to be consistent | 286 // Adjust these values even before calling super.deleteSurroundingText()
to be consistent |
374 // with the super class. | 287 // with the super class. |
375 if (isIndexBetweenUtf16SurrogatePair(editable, selectionStart - beforeLe
ngth)) { | 288 if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionStart - beforeL
ength)) { |
376 beforeLength += 1; | 289 beforeLength += 1; |
377 } | 290 } |
378 if (isIndexBetweenUtf16SurrogatePair(editable, selectionEnd + afterLengt
h)) { | 291 if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionEnd + afterLeng
th)) { |
379 afterLength += 1; | 292 afterLength += 1; |
380 } | 293 } |
381 | 294 |
382 super.deleteSurroundingText(beforeLength, afterLength); | 295 super.deleteSurroundingText(beforeLength, afterLength); |
383 updateSelectionIfRequired(); | 296 updateSelectionIfRequired(); |
384 | 297 |
385 // If this was called due to a physical key, no need to generate a key e
vent here as | 298 // 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. | 299 // the caller will take care of forwarding the original. |
387 if (fromPhysicalKey) { | 300 if (fromPhysicalKey) { |
388 return true; | 301 return true; |
389 } | 302 } |
390 | 303 |
391 return mImeAdapter.deleteSurroundingText(beforeLength, afterLength); | 304 return mImeAdapter.deleteSurroundingText(beforeLength, afterLength); |
392 } | 305 } |
393 | 306 |
394 /** | 307 /** |
| 308 * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent) |
| 309 */ |
| 310 @Override |
| 311 public boolean sendKeyEventOnUiThread(KeyEvent event) { |
| 312 return sendKeyEvent(event); |
| 313 } |
| 314 |
| 315 /** |
395 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) | 316 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) |
396 */ | 317 */ |
397 @Override | 318 @Override |
398 public boolean sendKeyEvent(KeyEvent event) { | 319 public boolean sendKeyEvent(KeyEvent event) { |
399 if (DEBUG_LOGS) { | 320 if (DEBUG_LOGS) { |
400 Log.w(TAG, "sendKeyEvent [%d] [%d] [%d]", event.getAction(), event.g
etKeyCode(), | 321 Log.w(TAG, "sendKeyEvent [%d] [%d] [%d]", event.getAction(), event.g
etKeyCode(), |
401 event.getUnicodeChar()); | 322 event.getUnicodeChar()); |
402 } | 323 } |
403 | 324 |
404 int action = event.getAction(); | 325 int action = event.getAction(); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
450 return true; | 371 return true; |
451 } | 372 } |
452 | 373 |
453 /** | 374 /** |
454 * Update the Editable to reflect what Blink will do in response to the KeyD
own for a | 375 * Update the Editable to reflect what Blink will do in response to the KeyD
own for a |
455 * unicode-mapped key event. | 376 * unicode-mapped key event. |
456 * @param unicodeChar The Unicode character to update selection with. | 377 * @param unicodeChar The Unicode character to update selection with. |
457 */ | 378 */ |
458 private void replaceSelectionWithUnicodeChar(int unicodeChar) { | 379 private void replaceSelectionWithUnicodeChar(int unicodeChar) { |
459 if (unicodeChar == 0) return; | 380 if (unicodeChar == 0) return; |
460 Editable editable = getEditableInternal(); | 381 int selectionStart = Selection.getSelectionStart(mEditable); |
461 int selectionStart = Selection.getSelectionStart(editable); | 382 int selectionEnd = Selection.getSelectionEnd(mEditable); |
462 int selectionEnd = Selection.getSelectionEnd(editable); | |
463 if (selectionStart > selectionEnd) { | 383 if (selectionStart > selectionEnd) { |
464 int temp = selectionStart; | 384 int temp = selectionStart; |
465 selectionStart = selectionEnd; | 385 selectionStart = selectionEnd; |
466 selectionEnd = temp; | 386 selectionEnd = temp; |
467 } | 387 } |
468 editable.replace(selectionStart, selectionEnd, Character.toString((char)
unicodeChar)); | 388 mEditable.replace(selectionStart, selectionEnd, Character.toString((char
) unicodeChar)); |
469 updateSelectionIfRequired(); | 389 updateSelectionIfRequired(); |
470 } | 390 } |
471 | 391 |
472 /** | 392 /** |
473 * @see BaseInputConnection#finishComposingText() | 393 * @see BaseInputConnection#finishComposingText() |
474 */ | 394 */ |
475 @Override | 395 @Override |
476 public boolean finishComposingText() { | 396 public boolean finishComposingText() { |
477 if (DEBUG_LOGS) Log.w(TAG, "finishComposingText"); | 397 if (DEBUG_LOGS) Log.w(TAG, "finishComposingText"); |
478 mPendingAccent = 0; | 398 mPendingAccent = 0; |
479 | 399 |
480 if (getComposingSpanStart(getEditableInternal()) | 400 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable))
{ |
481 == getComposingSpanEnd(getEditableInternal())) { | |
482 return true; | 401 return true; |
483 } | 402 } |
484 | 403 |
485 super.finishComposingText(); | 404 super.finishComposingText(); |
486 updateSelectionIfRequired(); | 405 updateSelectionIfRequired(); |
487 mImeAdapter.finishComposingText(); | 406 mImeAdapter.finishComposingText(); |
488 | 407 |
489 return true; | 408 return true; |
490 } | 409 } |
491 | 410 |
492 /** | 411 /** |
493 * @see BaseInputConnection#setSelection(int, int) | 412 * @see BaseInputConnection#setSelection(int, int) |
494 */ | 413 */ |
495 @Override | 414 @Override |
496 public boolean setSelection(int start, int end) { | 415 public boolean setSelection(int start, int end) { |
497 if (DEBUG_LOGS) Log.w(TAG, "setSelection [%d %d]", start, end); | 416 if (DEBUG_LOGS) Log.w(TAG, "setSelection [%d %d]", start, end); |
498 int textLength = getEditableInternal().length(); | 417 int textLength = mEditable.length(); |
499 if (start < 0 || end < 0 || start > textLength || end > textLength) retu
rn true; | 418 if (start < 0 || end < 0 || start > textLength || end > textLength) retu
rn true; |
500 super.setSelection(start, end); | 419 super.setSelection(start, end); |
501 updateSelectionIfRequired(); | 420 updateSelectionIfRequired(); |
502 return mImeAdapter.setEditableSelectionOffsets(start, end); | 421 return mImeAdapter.setEditableSelectionOffsets(start, end); |
503 } | 422 } |
504 | 423 |
505 /** | 424 /** |
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) | 425 * @see BaseInputConnection#setComposingRegion(int, int) |
516 */ | 426 */ |
517 @Override | 427 @Override |
518 public boolean setComposingRegion(int start, int end) { | 428 public boolean setComposingRegion(int start, int end) { |
519 if (DEBUG_LOGS) Log.w(TAG, "setComposingRegion [%d %d]", start, end); | 429 if (DEBUG_LOGS) Log.w(TAG, "setComposingRegion [%d %d]", start, end); |
520 Editable editable = getEditableInternal(); | 430 int textLength = mEditable.length(); |
521 int textLength = editable.length(); | |
522 int a = Math.min(start, end); | 431 int a = Math.min(start, end); |
523 int b = Math.max(start, end); | 432 int b = Math.max(start, end); |
524 if (a < 0) a = 0; | 433 if (a < 0) a = 0; |
525 if (b < 0) b = 0; | 434 if (b < 0) b = 0; |
526 if (a > textLength) a = textLength; | 435 if (a > textLength) a = textLength; |
527 if (b > textLength) b = textLength; | 436 if (b > textLength) b = textLength; |
528 | 437 |
529 if (a == b) { | 438 if (a == b) { |
530 removeComposingSpans(editable); | 439 removeComposingSpans(mEditable); |
531 } else { | 440 } else { |
532 super.setComposingRegion(a, b); | 441 super.setComposingRegion(a, b); |
533 } | 442 } |
534 updateSelectionIfRequired(); | 443 updateSelectionIfRequired(); |
| 444 return mImeAdapter.setComposingRegion(a, b); |
| 445 } |
535 | 446 |
536 CharSequence regionText = null; | 447 @Override |
537 if (b > a) { | 448 public void onRestartInputOnUiThread() { |
538 regionText = editable.subSequence(a, b); | 449 if (DEBUG_LOGS) Log.w(TAG, "onRestartInputOnUiThread"); |
539 } | 450 mNumNestedBatchEdits = 0; |
540 return mImeAdapter.setComposingRegion(regionText, a, b); | 451 mPendingAccent = 0; |
| 452 } |
| 453 |
| 454 @Override |
| 455 public void moveCursorToSelectionEndOnUiThread() { |
| 456 if (DEBUG_LOGS) Log.w(TAG, "movecursorToEnd"); |
| 457 int selectionEnd = Selection.getSelectionEnd(mEditable); |
| 458 setSelection(selectionEnd, selectionEnd); |
| 459 } |
| 460 |
| 461 @Override |
| 462 public void unblockOnUiThread() {} |
| 463 |
| 464 @Override |
| 465 public Handler getHandler() { |
| 466 return mHandler; |
541 } | 467 } |
542 | 468 |
543 @VisibleForTesting | 469 @VisibleForTesting |
544 static class ImeState { | 470 static class ImeState { |
545 public final String text; | 471 public final String text; |
546 public final int selectionStart; | 472 public final int selectionStart; |
547 public final int selectionEnd; | 473 public final int selectionEnd; |
548 public final int compositionStart; | 474 public final int compositionStart; |
549 public final int compositionEnd; | 475 public final int compositionEnd; |
550 | 476 |
551 public ImeState(String text, int selectionStart, int selectionEnd, | 477 public ImeState(String text, int selectionStart, int selectionEnd, int c
ompositionStart, |
552 int compositionStart, int compositionEnd) { | 478 int compositionEnd) { |
553 this.text = text; | 479 this.text = text; |
554 this.selectionStart = selectionStart; | 480 this.selectionStart = selectionStart; |
555 this.selectionEnd = selectionEnd; | 481 this.selectionEnd = selectionEnd; |
556 this.compositionStart = compositionStart; | 482 this.compositionStart = compositionStart; |
557 this.compositionEnd = compositionEnd; | 483 this.compositionEnd = compositionEnd; |
558 } | 484 } |
559 } | 485 } |
560 | 486 |
561 @VisibleForTesting | 487 @VisibleForTesting |
562 ImeState getImeStateForTesting() { | 488 ImeState getImeStateForTesting() { |
563 Editable editable = getEditableInternal(); | 489 String text = mEditable.toString(); |
564 String text = editable.toString(); | 490 int selectionStart = Selection.getSelectionStart(mEditable); |
565 int selectionStart = Selection.getSelectionStart(editable); | 491 int selectionEnd = Selection.getSelectionEnd(mEditable); |
566 int selectionEnd = Selection.getSelectionEnd(editable); | 492 int compositionStart = getComposingSpanStart(mEditable); |
567 int compositionStart = getComposingSpanStart(editable); | 493 int compositionEnd = getComposingSpanEnd(mEditable); |
568 int compositionEnd = getComposingSpanEnd(editable); | |
569 return new ImeState(text, selectionStart, selectionEnd, compositionStart
, compositionEnd); | 494 return new ImeState(text, selectionStart, selectionEnd, compositionStart
, compositionEnd); |
570 } | 495 } |
571 } | 496 } |
OLD | NEW |