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