AdapterInputConnection.java revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
1// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser.input; 6 7import com.google.common.annotations.VisibleForTesting; 8 9import android.text.Editable; 10import android.text.InputType; 11import android.text.Selection; 12import android.util.Log; 13import android.view.KeyEvent; 14import android.view.View; 15import android.view.inputmethod.BaseInputConnection; 16import android.view.inputmethod.EditorInfo; 17import android.view.inputmethod.ExtractedText; 18import android.view.inputmethod.ExtractedTextRequest; 19 20/** 21 * InputConnection is created by ContentView.onCreateInputConnection. 22 * It then adapts android's IME to chrome's RenderWidgetHostView using the 23 * native ImeAdapterAndroid via the class ImeAdapter. 24 */ 25public class AdapterInputConnection extends BaseInputConnection { 26 private static final String TAG = 27 "org.chromium.content.browser.input.AdapterInputConnection"; 28 private static final boolean DEBUG = false; 29 /** 30 * Selection value should be -1 if not known. See EditorInfo.java for details. 31 */ 32 public static final int INVALID_SELECTION = -1; 33 public static final int INVALID_COMPOSITION = -1; 34 35 private final View mInternalView; 36 private final ImeAdapter mImeAdapter; 37 38 private boolean mSingleLine; 39 private int mNumNestedBatchEdits = 0; 40 private boolean mIgnoreTextInputStateUpdates = false; 41 42 private int mLastUpdateSelectionStart = INVALID_SELECTION; 43 private int mLastUpdateSelectionEnd = INVALID_SELECTION; 44 private int mLastUpdateCompositionStart = INVALID_COMPOSITION; 45 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; 46 47 @VisibleForTesting 48 AdapterInputConnection(View view, ImeAdapter imeAdapter, EditorInfo outAttrs) { 49 super(view, true); 50 mInternalView = view; 51 mImeAdapter = imeAdapter; 52 mImeAdapter.setInputConnection(this); 53 mSingleLine = true; 54 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; 55 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT 56 | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; 57 58 if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeText) { 59 // Normal text field 60 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; 61 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeTextArea || 62 imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeContentEditable) { 63 // TextArea or contenteditable. 64 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE 65 | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES 66 | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; 67 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE; 68 mSingleLine = false; 69 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypePassword) { 70 // Password 71 outAttrs.inputType = InputType.TYPE_CLASS_TEXT 72 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; 73 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; 74 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeSearch) { 75 // Search 76 outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH; 77 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeUrl) { 78 // Url 79 // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so 80 // exclude it for now. 81 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; 82 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeEmail) { 83 // Email 84 outAttrs.inputType = InputType.TYPE_CLASS_TEXT 85 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; 86 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; 87 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeTel) { 88 // Telephone 89 // Number and telephone do not have both a Tab key and an 90 // action in default OSK, so set the action to NEXT 91 outAttrs.inputType = InputType.TYPE_CLASS_PHONE; 92 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 93 } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeNumber) { 94 // Number 95 outAttrs.inputType = InputType.TYPE_CLASS_NUMBER 96 | InputType.TYPE_NUMBER_VARIATION_NORMAL; 97 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 98 } 99 outAttrs.initialSelStart = imeAdapter.getInitialSelectionStart(); 100 outAttrs.initialSelEnd = imeAdapter.getInitialSelectionStart(); 101 } 102 103 /** 104 * Updates the AdapterInputConnection's internal representation of the text 105 * being edited and its selection and composition properties. The resulting 106 * Editable is accessible through the getEditable() method. 107 * If the text has not changed, this also calls updateSelection on the InputMethodManager. 108 * @param text The String contents of the field being edited 109 * @param selectionStart The character offset of the selection start, or the caret 110 * position if there is no selection 111 * @param selectionEnd The character offset of the selection end, or the caret 112 * position if there is no selection 113 * @param compositionStart The character offset of the composition start, or -1 114 * if there is no composition 115 * @param compositionEnd The character offset of the composition end, or -1 116 * if there is no selection 117 */ 118 public void setEditableText(String text, int selectionStart, int selectionEnd, 119 int compositionStart, int compositionEnd) { 120 if (DEBUG) { 121 Log.w(TAG, "setEditableText [" + text + "] [" + selectionStart + " " + selectionEnd 122 + "] [" + compositionStart + " " + compositionEnd + "]"); 123 } 124 // Non-breaking spaces can cause the IME to get confused. Replace with normal spaces. 125 text = text.replace('\u00A0', ' '); 126 127 Editable editable = getEditable(); 128 129 int prevSelectionStart = Selection.getSelectionStart(editable); 130 int prevSelectionEnd = Selection.getSelectionEnd(editable); 131 int prevCompositionStart = getComposingSpanStart(editable); 132 int prevCompositionEnd = getComposingSpanEnd(editable); 133 String prevText = editable.toString(); 134 135 selectionStart = Math.min(selectionStart, text.length()); 136 selectionEnd = Math.min(selectionEnd, text.length()); 137 compositionStart = Math.min(compositionStart, text.length()); 138 compositionEnd = Math.min(compositionEnd, text.length()); 139 140 boolean textUnchanged = prevText.equals(text); 141 142 if (!textUnchanged) { 143 editable.replace(0, editable.length(), text); 144 } 145 146 if (prevSelectionStart == selectionStart && prevSelectionEnd == selectionEnd 147 && prevCompositionStart == compositionStart 148 && prevCompositionEnd == compositionEnd) { 149 // Nothing has changed; don't need to do anything 150 return; 151 } 152 153 Selection.setSelection(editable, selectionStart, selectionEnd); 154 155 if (compositionStart == compositionEnd) { 156 removeComposingSpans(editable); 157 } else { 158 super.setComposingRegion(compositionStart, compositionEnd); 159 } 160 161 if (mIgnoreTextInputStateUpdates) return; 162 updateSelection(selectionStart, selectionEnd, compositionStart, compositionEnd); 163 } 164 165 @VisibleForTesting 166 protected void updateSelection( 167 int selectionStart, int selectionEnd, 168 int compositionStart, int compositionEnd) { 169 // Avoid sending update if we sent an exact update already previously. 170 if (mLastUpdateSelectionStart == selectionStart && 171 mLastUpdateSelectionEnd == selectionEnd && 172 mLastUpdateCompositionStart == compositionStart && 173 mLastUpdateCompositionEnd == compositionEnd) { 174 return; 175 } 176 if (DEBUG) { 177 Log.w(TAG, "updateSelection [" + selectionStart + " " + selectionEnd + "] [" 178 + compositionStart + " " + compositionEnd + "]"); 179 } 180 // updateSelection should be called every time the selection or composition changes 181 // if it happens not within a batch edit, or at the end of each top level batch edit. 182 getInputMethodManagerWrapper().updateSelection(mInternalView, 183 selectionStart, selectionEnd, compositionStart, compositionEnd); 184 mLastUpdateSelectionStart = selectionStart; 185 mLastUpdateSelectionEnd = selectionEnd; 186 mLastUpdateCompositionStart = compositionStart; 187 mLastUpdateCompositionEnd = compositionEnd; 188 } 189 190 /** 191 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) 192 */ 193 @Override 194 public boolean setComposingText(CharSequence text, int newCursorPosition) { 195 if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPosition + "]"); 196 super.setComposingText(text, newCursorPosition); 197 return mImeAdapter.checkCompositionQueueAndCallNative(text.toString(), 198 newCursorPosition, false); 199 } 200 201 /** 202 * @see BaseInputConnection#commitText(java.lang.CharSequence, int) 203 */ 204 @Override 205 public boolean commitText(CharSequence text, int newCursorPosition) { 206 if (DEBUG) Log.w(TAG, "commitText [" + text + "] [" + newCursorPosition + "]"); 207 super.commitText(text, newCursorPosition); 208 return mImeAdapter.checkCompositionQueueAndCallNative(text.toString(), 209 newCursorPosition, text.length() > 0); 210 } 211 212 /** 213 * @see BaseInputConnection#performEditorAction(int) 214 */ 215 @Override 216 public boolean performEditorAction(int actionCode) { 217 if (DEBUG) Log.w(TAG, "performEditorAction [" + actionCode + "]"); 218 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 219 restartInput(); 220 // Send TAB key event 221 long timeStampMs = System.currentTimeMillis(); 222 mImeAdapter.sendSyntheticKeyEvent( 223 ImeAdapter.sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0); 224 } else { 225 mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER, 226 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 227 | KeyEvent.FLAG_EDITOR_ACTION); 228 } 229 return true; 230 } 231 232 /** 233 * @see BaseInputConnection#performContextMenuAction(int) 234 */ 235 @Override 236 public boolean performContextMenuAction(int id) { 237 if (DEBUG) Log.w(TAG, "performContextMenuAction [" + id + "]"); 238 switch (id) { 239 case android.R.id.selectAll: 240 return mImeAdapter.selectAll(); 241 case android.R.id.cut: 242 return mImeAdapter.cut(); 243 case android.R.id.copy: 244 return mImeAdapter.copy(); 245 case android.R.id.paste: 246 return mImeAdapter.paste(); 247 default: 248 return false; 249 } 250 } 251 252 /** 253 * @see BaseInputConnection#getExtractedText(android.view.inputmethod.ExtractedTextRequest, 254 * int) 255 */ 256 @Override 257 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 258 if (DEBUG) Log.w(TAG, "getExtractedText"); 259 ExtractedText et = new ExtractedText(); 260 Editable editable = getEditable(); 261 et.text = editable.toString(); 262 et.partialEndOffset = editable.length(); 263 et.selectionStart = Selection.getSelectionStart(editable); 264 et.selectionEnd = Selection.getSelectionEnd(editable); 265 et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; 266 return et; 267 } 268 269 /** 270 * @see BaseInputConnection#beginBatchEdit() 271 */ 272 @Override 273 public boolean beginBatchEdit() { 274 if (DEBUG) Log.w(TAG, "beginBatchEdit [" + (mNumNestedBatchEdits == 0) + "]"); 275 if (mNumNestedBatchEdits == 0) mImeAdapter.batchStateChanged(true); 276 277 mNumNestedBatchEdits++; 278 return false; 279 } 280 281 /** 282 * @see BaseInputConnection#endBatchEdit() 283 */ 284 @Override 285 public boolean endBatchEdit() { 286 if (mNumNestedBatchEdits == 0) return false; 287 288 --mNumNestedBatchEdits; 289 if (DEBUG) Log.w(TAG, "endBatchEdit [" + (mNumNestedBatchEdits == 0) + "]"); 290 if (mNumNestedBatchEdits == 0) mImeAdapter.batchStateChanged(false); 291 return false; 292 } 293 294 /** 295 * @see BaseInputConnection#deleteSurroundingText(int, int) 296 */ 297 @Override 298 public boolean deleteSurroundingText(int leftLength, int rightLength) { 299 if (DEBUG) { 300 Log.w(TAG, "deleteSurroundingText [" + leftLength + " " + rightLength + "]"); 301 } 302 if (!super.deleteSurroundingText(leftLength, rightLength)) { 303 return false; 304 } 305 return mImeAdapter.deleteSurroundingText(leftLength, rightLength); 306 } 307 308 /** 309 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) 310 */ 311 @Override 312 public boolean sendKeyEvent(KeyEvent event) { 313 if (DEBUG) Log.w(TAG, "sendKeyEvent [" + event.getAction() + "]"); 314 mImeAdapter.hideSelectionAndInsertionHandleControllers(); 315 316 // If this is a key-up, and backspace/del or if the key has a character representation, 317 // need to update the underlying Editable (i.e. the local representation of the text 318 // being edited). 319 if (event.getAction() == KeyEvent.ACTION_UP) { 320 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { 321 super.deleteSurroundingText(1, 0); 322 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { 323 super.deleteSurroundingText(0, 1); 324 } else { 325 int unicodeChar = event.getUnicodeChar(); 326 if (unicodeChar != 0) { 327 Editable editable = getEditable(); 328 int selectionStart = Selection.getSelectionStart(editable); 329 int selectionEnd = Selection.getSelectionEnd(editable); 330 if (selectionStart > selectionEnd) { 331 int temp = selectionStart; 332 selectionStart = selectionEnd; 333 selectionEnd = temp; 334 } 335 editable.replace(selectionStart, selectionEnd, 336 Character.toString((char)unicodeChar)); 337 } 338 } 339 } 340 mImeAdapter.translateAndSendNativeEvents(event); 341 return true; 342 } 343 344 /** 345 * @see BaseInputConnection#finishComposingText() 346 */ 347 @Override 348 public boolean finishComposingText() { 349 if (DEBUG) Log.w(TAG, "finishComposingText"); 350 Editable editable = getEditable(); 351 if (getComposingSpanStart(editable) == getComposingSpanEnd(editable)) { 352 return true; 353 } 354 355 // TODO(aurimas): remove this workaround of changing composition before confirmComposition 356 // Blink should support keeping the cursor (http://crbug.com/239923) 357 int selectionStart = Selection.getSelectionStart(editable); 358 int compositionStart = getComposingSpanStart(editable); 359 super.finishComposingText(); 360 361 beginBatchEdit(); 362 if (compositionStart != -1 && compositionStart < selectionStart 363 && !mImeAdapter.setComposingRegion(compositionStart, selectionStart)) { 364 return false; 365 } 366 if (!mImeAdapter.checkCompositionQueueAndCallNative("", 0, true)) return false; 367 endBatchEdit(); 368 return true; 369 } 370 371 /** 372 * @see BaseInputConnection#setSelection(int, int) 373 */ 374 @Override 375 public boolean setSelection(int start, int end) { 376 if (DEBUG) Log.w(TAG, "setSelection"); 377 if (start < 0 || end < 0) return true; 378 super.setSelection(start, end); 379 return mImeAdapter.setEditableSelectionOffsets(start, end); 380 } 381 382 /** 383 * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text 384 * state is no longer what the IME has and that it needs to be updated. 385 */ 386 void restartInput() { 387 if (DEBUG) Log.w(TAG, "restartInput"); 388 getInputMethodManagerWrapper().restartInput(mInternalView); 389 mIgnoreTextInputStateUpdates = false; 390 mNumNestedBatchEdits = 0; 391 } 392 393 /** 394 * @see BaseInputConnection#setComposingRegion(int, int) 395 */ 396 @Override 397 public boolean setComposingRegion(int start, int end) { 398 if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]"); 399 int a = Math.min(start, end); 400 int b = Math.max(start, end); 401 if (a < 0) a = 0; 402 if (b < 0) b = 0; 403 404 if (a == b) { 405 removeComposingSpans(getEditable()); 406 } else { 407 super.setComposingRegion(a, b); 408 } 409 return mImeAdapter.setComposingRegion(a, b); 410 } 411 412 boolean isActive() { 413 return getInputMethodManagerWrapper().isActive(mInternalView); 414 } 415 416 public void setIgnoreTextInputStateUpdates(boolean shouldIgnore) { 417 mIgnoreTextInputStateUpdates = shouldIgnore; 418 if (shouldIgnore) return; 419 420 Editable editable = getEditable(); 421 updateSelection(Selection.getSelectionStart(editable), 422 Selection.getSelectionEnd(editable), 423 getComposingSpanStart(editable), 424 getComposingSpanEnd(editable)); 425 } 426 427 @VisibleForTesting 428 protected boolean isIgnoringTextInputStateUpdates() { 429 return mIgnoreTextInputStateUpdates; 430 } 431 432 private InputMethodManagerWrapper getInputMethodManagerWrapper() { 433 return mImeAdapter.getInputMethodManagerWrapper(); 434 } 435} 436