1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package android.view.inputmethod; 18 19 import android.annotation.CallSuper; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.SystemClock; 25 import android.text.Editable; 26 import android.text.NoCopySpan; 27 import android.text.Selection; 28 import android.text.Spannable; 29 import android.text.SpannableStringBuilder; 30 import android.text.Spanned; 31 import android.text.TextUtils; 32 import android.text.method.MetaKeyKeyListener; 33 import android.util.Log; 34 import android.util.LogPrinter; 35 import android.view.KeyCharacterMap; 36 import android.view.KeyEvent; 37 import android.view.View; 38 39 class ComposingText implements NoCopySpan { 40 } 41 42 /** 43 * Base class for implementors of the InputConnection interface, taking care 44 * of most of the common behavior for providing a connection to an Editable. 45 * Implementors of this class will want to be sure to implement 46 * {@link #getEditable} to provide access to their own editable object, and 47 * to refer to the documentation in {@link InputConnection}. 48 */ 49 public class BaseInputConnection implements InputConnection { 50 private static final boolean DEBUG = false; 51 private static final String TAG = "BaseInputConnection"; 52 static final Object COMPOSING = new ComposingText(); 53 54 /** @hide */ 55 protected final InputMethodManager mIMM; 56 final View mTargetView; 57 final boolean mDummyMode; 58 59 private Object[] mDefaultComposingSpans; 60 61 Editable mEditable; 62 KeyCharacterMap mKeyCharacterMap; 63 BaseInputConnection(InputMethodManager mgr, boolean fullEditor)64 BaseInputConnection(InputMethodManager mgr, boolean fullEditor) { 65 mIMM = mgr; 66 mTargetView = null; 67 mDummyMode = !fullEditor; 68 } 69 BaseInputConnection(View targetView, boolean fullEditor)70 public BaseInputConnection(View targetView, boolean fullEditor) { 71 mIMM = (InputMethodManager)targetView.getContext().getSystemService( 72 Context.INPUT_METHOD_SERVICE); 73 mTargetView = targetView; 74 mDummyMode = !fullEditor; 75 } 76 removeComposingSpans(Spannable text)77 public static final void removeComposingSpans(Spannable text) { 78 text.removeSpan(COMPOSING); 79 Object[] sps = text.getSpans(0, text.length(), Object.class); 80 if (sps != null) { 81 for (int i=sps.length-1; i>=0; i--) { 82 Object o = sps[i]; 83 if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { 84 text.removeSpan(o); 85 } 86 } 87 } 88 } 89 setComposingSpans(Spannable text)90 public static void setComposingSpans(Spannable text) { 91 setComposingSpans(text, 0, text.length()); 92 } 93 94 /** @hide */ setComposingSpans(Spannable text, int start, int end)95 public static void setComposingSpans(Spannable text, int start, int end) { 96 final Object[] sps = text.getSpans(start, end, Object.class); 97 if (sps != null) { 98 for (int i=sps.length-1; i>=0; i--) { 99 final Object o = sps[i]; 100 if (o == COMPOSING) { 101 text.removeSpan(o); 102 continue; 103 } 104 105 final int fl = text.getSpanFlags(o); 106 if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK)) 107 != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { 108 text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), 109 (fl & ~Spanned.SPAN_POINT_MARK_MASK) 110 | Spanned.SPAN_COMPOSING 111 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 112 } 113 } 114 } 115 116 text.setSpan(COMPOSING, start, end, 117 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 118 } 119 getComposingSpanStart(Spannable text)120 public static int getComposingSpanStart(Spannable text) { 121 return text.getSpanStart(COMPOSING); 122 } 123 getComposingSpanEnd(Spannable text)124 public static int getComposingSpanEnd(Spannable text) { 125 return text.getSpanEnd(COMPOSING); 126 } 127 128 /** 129 * Return the target of edit operations. The default implementation 130 * returns its own fake editable that is just used for composing text; 131 * subclasses that are real text editors should override this and 132 * supply their own. 133 */ getEditable()134 public Editable getEditable() { 135 if (mEditable == null) { 136 mEditable = Editable.Factory.getInstance().newEditable(""); 137 Selection.setSelection(mEditable, 0); 138 } 139 return mEditable; 140 } 141 142 /** 143 * Default implementation does nothing. 144 */ beginBatchEdit()145 public boolean beginBatchEdit() { 146 return false; 147 } 148 149 /** 150 * Default implementation does nothing. 151 */ endBatchEdit()152 public boolean endBatchEdit() { 153 return false; 154 } 155 156 /** 157 * Default implementation calls {@link #finishComposingText()}. 158 */ 159 @CallSuper closeConnection()160 public void closeConnection() { 161 finishComposingText(); 162 } 163 164 /** 165 * Default implementation uses 166 * {@link MetaKeyKeyListener#clearMetaKeyState(long, int) 167 * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state. 168 */ clearMetaKeyStates(int states)169 public boolean clearMetaKeyStates(int states) { 170 final Editable content = getEditable(); 171 if (content == null) return false; 172 MetaKeyKeyListener.clearMetaKeyState(content, states); 173 return true; 174 } 175 176 /** 177 * Default implementation does nothing and returns false. 178 */ commitCompletion(CompletionInfo text)179 public boolean commitCompletion(CompletionInfo text) { 180 return false; 181 } 182 183 /** 184 * Default implementation does nothing and returns false. 185 */ commitCorrection(CorrectionInfo correctionInfo)186 public boolean commitCorrection(CorrectionInfo correctionInfo) { 187 return false; 188 } 189 190 /** 191 * Default implementation replaces any existing composing text with 192 * the given text. In addition, only if dummy mode, a key event is 193 * sent for the new text and the current editable buffer cleared. 194 */ commitText(CharSequence text, int newCursorPosition)195 public boolean commitText(CharSequence text, int newCursorPosition) { 196 if (DEBUG) Log.v(TAG, "commitText " + text); 197 replaceText(text, newCursorPosition, false); 198 sendCurrentText(); 199 return true; 200 } 201 202 /** 203 * The default implementation performs the deletion around the current selection position of the 204 * editable text. 205 * 206 * @param beforeLength The number of characters before the cursor to be deleted, in code unit. 207 * If this is greater than the number of existing characters between the beginning of the 208 * text and the cursor, then this method does not fail but deletes all the characters in 209 * that range. 210 * @param afterLength The number of characters after the cursor to be deleted, in code unit. 211 * If this is greater than the number of existing characters between the cursor and 212 * the end of the text, then this method does not fail but deletes all the characters in 213 * that range. 214 */ deleteSurroundingText(int beforeLength, int afterLength)215 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 216 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength 217 + " / " + afterLength); 218 final Editable content = getEditable(); 219 if (content == null) return false; 220 221 beginBatchEdit(); 222 223 int a = Selection.getSelectionStart(content); 224 int b = Selection.getSelectionEnd(content); 225 226 if (a > b) { 227 int tmp = a; 228 a = b; 229 b = tmp; 230 } 231 232 // Ignore the composing text. 233 int ca = getComposingSpanStart(content); 234 int cb = getComposingSpanEnd(content); 235 if (cb < ca) { 236 int tmp = ca; 237 ca = cb; 238 cb = tmp; 239 } 240 if (ca != -1 && cb != -1) { 241 if (ca < a) a = ca; 242 if (cb > b) b = cb; 243 } 244 245 int deleted = 0; 246 247 if (beforeLength > 0) { 248 int start = a - beforeLength; 249 if (start < 0) start = 0; 250 content.delete(start, a); 251 deleted = a - start; 252 } 253 254 if (afterLength > 0) { 255 b = b - deleted; 256 257 int end = b + afterLength; 258 if (end > content.length()) end = content.length(); 259 260 content.delete(b, end); 261 } 262 263 endBatchEdit(); 264 265 return true; 266 } 267 268 private static int INVALID_INDEX = -1; findIndexBackward(final CharSequence cs, final int from, final int numCodePoints)269 private static int findIndexBackward(final CharSequence cs, final int from, 270 final int numCodePoints) { 271 int currentIndex = from; 272 boolean waitingHighSurrogate = false; 273 final int N = cs.length(); 274 if (currentIndex < 0 || N < currentIndex) { 275 return INVALID_INDEX; // The starting point is out of range. 276 } 277 if (numCodePoints < 0) { 278 return INVALID_INDEX; // Basically this should not happen. 279 } 280 int remainingCodePoints = numCodePoints; 281 while (true) { 282 if (remainingCodePoints == 0) { 283 return currentIndex; // Reached to the requested length in code points. 284 } 285 286 --currentIndex; 287 if (currentIndex < 0) { 288 if (waitingHighSurrogate) { 289 return INVALID_INDEX; // An invalid surrogate pair is found. 290 } 291 return 0; // Reached to the beginning of the text w/o any invalid surrogate pair. 292 } 293 final char c = cs.charAt(currentIndex); 294 if (waitingHighSurrogate) { 295 if (!java.lang.Character.isHighSurrogate(c)) { 296 return INVALID_INDEX; // An invalid surrogate pair is found. 297 } 298 waitingHighSurrogate = false; 299 --remainingCodePoints; 300 continue; 301 } 302 if (!java.lang.Character.isSurrogate(c)) { 303 --remainingCodePoints; 304 continue; 305 } 306 if (java.lang.Character.isHighSurrogate(c)) { 307 return INVALID_INDEX; // A invalid surrogate pair is found. 308 } 309 waitingHighSurrogate = true; 310 } 311 } 312 findIndexForward(final CharSequence cs, final int from, final int numCodePoints)313 private static int findIndexForward(final CharSequence cs, final int from, 314 final int numCodePoints) { 315 int currentIndex = from; 316 boolean waitingLowSurrogate = false; 317 final int N = cs.length(); 318 if (currentIndex < 0 || N < currentIndex) { 319 return INVALID_INDEX; // The starting point is out of range. 320 } 321 if (numCodePoints < 0) { 322 return INVALID_INDEX; // Basically this should not happen. 323 } 324 int remainingCodePoints = numCodePoints; 325 326 while (true) { 327 if (remainingCodePoints == 0) { 328 return currentIndex; // Reached to the requested length in code points. 329 } 330 331 if (currentIndex >= N) { 332 if (waitingLowSurrogate) { 333 return INVALID_INDEX; // An invalid surrogate pair is found. 334 } 335 return N; // Reached to the end of the text w/o any invalid surrogate pair. 336 } 337 final char c = cs.charAt(currentIndex); 338 if (waitingLowSurrogate) { 339 if (!java.lang.Character.isLowSurrogate(c)) { 340 return INVALID_INDEX; // An invalid surrogate pair is found. 341 } 342 --remainingCodePoints; 343 waitingLowSurrogate = false; 344 ++currentIndex; 345 continue; 346 } 347 if (!java.lang.Character.isSurrogate(c)) { 348 --remainingCodePoints; 349 ++currentIndex; 350 continue; 351 } 352 if (java.lang.Character.isLowSurrogate(c)) { 353 return INVALID_INDEX; // A invalid surrogate pair is found. 354 } 355 waitingLowSurrogate = true; 356 ++currentIndex; 357 } 358 } 359 360 /** 361 * The default implementation performs the deletion around the current selection position of the 362 * editable text. 363 * @param beforeLength The number of characters before the cursor to be deleted, in code points. 364 * If this is greater than the number of existing characters between the beginning of the 365 * text and the cursor, then this method does not fail but deletes all the characters in 366 * that range. 367 * @param afterLength The number of characters after the cursor to be deleted, in code points. 368 * If this is greater than the number of existing characters between the cursor and 369 * the end of the text, then this method does not fail but deletes all the characters in 370 * that range. 371 */ deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)372 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 373 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength 374 + " / " + afterLength); 375 final Editable content = getEditable(); 376 if (content == null) return false; 377 378 beginBatchEdit(); 379 380 int a = Selection.getSelectionStart(content); 381 int b = Selection.getSelectionEnd(content); 382 383 if (a > b) { 384 int tmp = a; 385 a = b; 386 b = tmp; 387 } 388 389 // Ignore the composing text. 390 int ca = getComposingSpanStart(content); 391 int cb = getComposingSpanEnd(content); 392 if (cb < ca) { 393 int tmp = ca; 394 ca = cb; 395 cb = tmp; 396 } 397 if (ca != -1 && cb != -1) { 398 if (ca < a) a = ca; 399 if (cb > b) b = cb; 400 } 401 402 if (a >= 0 && b >= 0) { 403 final int start = findIndexBackward(content, a, Math.max(beforeLength, 0)); 404 if (start != INVALID_INDEX) { 405 final int end = findIndexForward(content, b, Math.max(afterLength, 0)); 406 if (end != INVALID_INDEX) { 407 final int numDeleteBefore = a - start; 408 if (numDeleteBefore > 0) { 409 content.delete(start, a); 410 } 411 final int numDeleteAfter = end - b; 412 if (numDeleteAfter > 0) { 413 content.delete(b - numDeleteBefore, end - numDeleteBefore); 414 } 415 } 416 } 417 // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX, 418 // but the truth is that IInputConnectionWrapper running in the middle of IPC calls 419 // always returns true to the IME without waiting for the completion of this method as 420 // IInputConnectionWrapper#isAtive() returns true. This is actually why some methods 421 // including this method look like asynchronous calls from the IME. 422 } 423 424 endBatchEdit(); 425 426 return true; 427 } 428 429 /** 430 * The default implementation removes the composing state from the 431 * current editable text. In addition, only if dummy mode, a key event is 432 * sent for the new text and the current editable buffer cleared. 433 */ finishComposingText()434 public boolean finishComposingText() { 435 if (DEBUG) Log.v(TAG, "finishComposingText"); 436 final Editable content = getEditable(); 437 if (content != null) { 438 beginBatchEdit(); 439 removeComposingSpans(content); 440 // Note: sendCurrentText does nothing unless mDummyMode is set 441 sendCurrentText(); 442 endBatchEdit(); 443 } 444 return true; 445 } 446 447 /** 448 * The default implementation uses TextUtils.getCapsMode to get the 449 * cursor caps mode for the current selection position in the editable 450 * text, unless in dummy mode in which case 0 is always returned. 451 */ getCursorCapsMode(int reqModes)452 public int getCursorCapsMode(int reqModes) { 453 if (mDummyMode) return 0; 454 455 final Editable content = getEditable(); 456 if (content == null) return 0; 457 458 int a = Selection.getSelectionStart(content); 459 int b = Selection.getSelectionEnd(content); 460 461 if (a > b) { 462 int tmp = a; 463 a = b; 464 b = tmp; 465 } 466 467 return TextUtils.getCapsMode(content, a, reqModes); 468 } 469 470 /** 471 * The default implementation always returns null. 472 */ getExtractedText(ExtractedTextRequest request, int flags)473 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 474 return null; 475 } 476 477 /** 478 * The default implementation returns the given amount of text from the 479 * current cursor position in the buffer. 480 */ getTextBeforeCursor(int length, int flags)481 public CharSequence getTextBeforeCursor(int length, int flags) { 482 final Editable content = getEditable(); 483 if (content == null) return null; 484 485 int a = Selection.getSelectionStart(content); 486 int b = Selection.getSelectionEnd(content); 487 488 if (a > b) { 489 int tmp = a; 490 a = b; 491 b = tmp; 492 } 493 494 if (a <= 0) { 495 return ""; 496 } 497 498 if (length > a) { 499 length = a; 500 } 501 502 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 503 return content.subSequence(a - length, a); 504 } 505 return TextUtils.substring(content, a - length, a); 506 } 507 508 /** 509 * The default implementation returns the text currently selected, or null if none is 510 * selected. 511 */ getSelectedText(int flags)512 public CharSequence getSelectedText(int flags) { 513 final Editable content = getEditable(); 514 if (content == null) return null; 515 516 int a = Selection.getSelectionStart(content); 517 int b = Selection.getSelectionEnd(content); 518 519 if (a > b) { 520 int tmp = a; 521 a = b; 522 b = tmp; 523 } 524 525 if (a == b || a < 0) return null; 526 527 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 528 return content.subSequence(a, b); 529 } 530 return TextUtils.substring(content, a, b); 531 } 532 533 /** 534 * The default implementation returns the given amount of text from the 535 * current cursor position in the buffer. 536 */ getTextAfterCursor(int length, int flags)537 public CharSequence getTextAfterCursor(int length, int flags) { 538 final Editable content = getEditable(); 539 if (content == null) return null; 540 541 int a = Selection.getSelectionStart(content); 542 int b = Selection.getSelectionEnd(content); 543 544 if (a > b) { 545 int tmp = a; 546 a = b; 547 b = tmp; 548 } 549 550 // Guard against the case where the cursor has not been positioned yet. 551 if (b < 0) { 552 b = 0; 553 } 554 555 if (b + length > content.length()) { 556 length = content.length() - b; 557 } 558 559 560 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 561 return content.subSequence(b, b + length); 562 } 563 return TextUtils.substring(content, b, b + length); 564 } 565 566 /** 567 * The default implementation turns this into the enter key. 568 */ performEditorAction(int actionCode)569 public boolean performEditorAction(int actionCode) { 570 long eventTime = SystemClock.uptimeMillis(); 571 sendKeyEvent(new KeyEvent(eventTime, eventTime, 572 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 573 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 574 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 575 | KeyEvent.FLAG_EDITOR_ACTION)); 576 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 577 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 578 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 579 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 580 | KeyEvent.FLAG_EDITOR_ACTION)); 581 return true; 582 } 583 584 /** 585 * The default implementation does nothing. 586 */ performContextMenuAction(int id)587 public boolean performContextMenuAction(int id) { 588 return false; 589 } 590 591 /** 592 * The default implementation does nothing. 593 */ performPrivateCommand(String action, Bundle data)594 public boolean performPrivateCommand(String action, Bundle data) { 595 return false; 596 } 597 598 /** 599 * The default implementation does nothing. 600 */ requestCursorUpdates(int cursorUpdateMode)601 public boolean requestCursorUpdates(int cursorUpdateMode) { 602 return false; 603 } 604 getHandler()605 public Handler getHandler() { 606 return null; 607 } 608 609 /** 610 * The default implementation places the given text into the editable, 611 * replacing any existing composing text. The new text is marked as 612 * in a composing state with the composing style. 613 */ setComposingText(CharSequence text, int newCursorPosition)614 public boolean setComposingText(CharSequence text, int newCursorPosition) { 615 if (DEBUG) Log.v(TAG, "setComposingText " + text); 616 replaceText(text, newCursorPosition, true); 617 return true; 618 } 619 setComposingRegion(int start, int end)620 public boolean setComposingRegion(int start, int end) { 621 final Editable content = getEditable(); 622 if (content != null) { 623 beginBatchEdit(); 624 removeComposingSpans(content); 625 int a = start; 626 int b = end; 627 if (a > b) { 628 int tmp = a; 629 a = b; 630 b = tmp; 631 } 632 // Clip the end points to be within the content bounds. 633 final int length = content.length(); 634 if (a < 0) a = 0; 635 if (b < 0) b = 0; 636 if (a > length) a = length; 637 if (b > length) b = length; 638 639 ensureDefaultComposingSpans(); 640 if (mDefaultComposingSpans != null) { 641 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 642 content.setSpan(mDefaultComposingSpans[i], a, b, 643 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 644 } 645 } 646 647 content.setSpan(COMPOSING, a, b, 648 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 649 650 // Note: sendCurrentText does nothing unless mDummyMode is set 651 sendCurrentText(); 652 endBatchEdit(); 653 } 654 return true; 655 } 656 657 /** 658 * The default implementation changes the selection position in the 659 * current editable text. 660 */ setSelection(int start, int end)661 public boolean setSelection(int start, int end) { 662 if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); 663 final Editable content = getEditable(); 664 if (content == null) return false; 665 int len = content.length(); 666 if (start > len || end > len || start < 0 || end < 0) { 667 // If the given selection is out of bounds, just ignore it. 668 // Most likely the text was changed out from under the IME, 669 // and the IME is going to have to update all of its state 670 // anyway. 671 return true; 672 } 673 if (start == end && MetaKeyKeyListener.getMetaState(content, 674 MetaKeyKeyListener.META_SELECTING) != 0) { 675 // If we are in selection mode, then we want to extend the 676 // selection instead of replacing it. 677 Selection.extendSelection(content, start); 678 } else { 679 Selection.setSelection(content, start, end); 680 } 681 return true; 682 } 683 684 /** 685 * Provides standard implementation for sending a key event to the window 686 * attached to the input connection's view. 687 */ sendKeyEvent(KeyEvent event)688 public boolean sendKeyEvent(KeyEvent event) { 689 mIMM.dispatchKeyEventFromInputMethod(mTargetView, event); 690 return false; 691 } 692 693 /** 694 * Updates InputMethodManager with the current fullscreen mode. 695 */ reportFullscreenMode(boolean enabled)696 public boolean reportFullscreenMode(boolean enabled) { 697 return true; 698 } 699 sendCurrentText()700 private void sendCurrentText() { 701 if (!mDummyMode) { 702 return; 703 } 704 705 Editable content = getEditable(); 706 if (content != null) { 707 final int N = content.length(); 708 if (N == 0) { 709 return; 710 } 711 if (N == 1) { 712 // If it's 1 character, we have a chance of being 713 // able to generate normal key events... 714 if (mKeyCharacterMap == null) { 715 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 716 } 717 char[] chars = new char[1]; 718 content.getChars(0, 1, chars, 0); 719 KeyEvent[] events = mKeyCharacterMap.getEvents(chars); 720 if (events != null) { 721 for (int i=0; i<events.length; i++) { 722 if (DEBUG) Log.v(TAG, "Sending: " + events[i]); 723 sendKeyEvent(events[i]); 724 } 725 content.clear(); 726 return; 727 } 728 } 729 730 // Otherwise, revert to the special key event containing 731 // the actual characters. 732 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), 733 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0); 734 sendKeyEvent(event); 735 content.clear(); 736 } 737 } 738 ensureDefaultComposingSpans()739 private void ensureDefaultComposingSpans() { 740 if (mDefaultComposingSpans == null) { 741 Context context; 742 if (mTargetView != null) { 743 context = mTargetView.getContext(); 744 } else if (mIMM.mServedView != null) { 745 context = mIMM.mServedView.getContext(); 746 } else { 747 context = null; 748 } 749 if (context != null) { 750 TypedArray ta = context.getTheme() 751 .obtainStyledAttributes(new int[] { 752 com.android.internal.R.attr.candidatesTextStyleSpans 753 }); 754 CharSequence style = ta.getText(0); 755 ta.recycle(); 756 if (style != null && style instanceof Spanned) { 757 mDefaultComposingSpans = ((Spanned)style).getSpans( 758 0, style.length(), Object.class); 759 } 760 } 761 } 762 } 763 replaceText(CharSequence text, int newCursorPosition, boolean composing)764 private void replaceText(CharSequence text, int newCursorPosition, 765 boolean composing) { 766 final Editable content = getEditable(); 767 if (content == null) { 768 return; 769 } 770 771 beginBatchEdit(); 772 773 // delete composing text set previously. 774 int a = getComposingSpanStart(content); 775 int b = getComposingSpanEnd(content); 776 777 if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b); 778 779 if (b < a) { 780 int tmp = a; 781 a = b; 782 b = tmp; 783 } 784 785 if (a != -1 && b != -1) { 786 removeComposingSpans(content); 787 } else { 788 a = Selection.getSelectionStart(content); 789 b = Selection.getSelectionEnd(content); 790 if (a < 0) a = 0; 791 if (b < 0) b = 0; 792 if (b < a) { 793 int tmp = a; 794 a = b; 795 b = tmp; 796 } 797 } 798 799 if (composing) { 800 Spannable sp = null; 801 if (!(text instanceof Spannable)) { 802 sp = new SpannableStringBuilder(text); 803 text = sp; 804 ensureDefaultComposingSpans(); 805 if (mDefaultComposingSpans != null) { 806 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 807 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(), 808 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 809 } 810 } 811 } else { 812 sp = (Spannable)text; 813 } 814 setComposingSpans(sp); 815 } 816 817 if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \"" 818 + text + "\", composing=" + composing 819 + ", type=" + text.getClass().getCanonicalName()); 820 821 if (DEBUG) { 822 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 823 lp.println("Current text:"); 824 TextUtils.dumpSpans(content, lp, " "); 825 lp.println("Composing text:"); 826 TextUtils.dumpSpans(text, lp, " "); 827 } 828 829 // Position the cursor appropriately, so that after replacing the 830 // desired range of text it will be located in the correct spot. 831 // This allows us to deal with filters performing edits on the text 832 // we are providing here. 833 if (newCursorPosition > 0) { 834 newCursorPosition += b - 1; 835 } else { 836 newCursorPosition += a; 837 } 838 if (newCursorPosition < 0) newCursorPosition = 0; 839 if (newCursorPosition > content.length()) 840 newCursorPosition = content.length(); 841 Selection.setSelection(content, newCursorPosition); 842 843 content.replace(a, b, text); 844 845 if (DEBUG) { 846 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 847 lp.println("Final text:"); 848 TextUtils.dumpSpans(content, lp, " "); 849 } 850 851 endBatchEdit(); 852 } 853 854 /** 855 * The default implementation does nothing. 856 */ commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)857 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { 858 return false; 859 } 860 } 861