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 * 215 * @return {@code true} when selected text is deleted, {@code false} when either the 216 * selection is invalid or not yet attached (i.e. selection start or end is -1), 217 * or the editable text is {@code null}. 218 */ deleteSurroundingText(int beforeLength, int afterLength)219 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 220 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength 221 + " / " + afterLength); 222 final Editable content = getEditable(); 223 if (content == null) return false; 224 225 beginBatchEdit(); 226 227 int a = Selection.getSelectionStart(content); 228 int b = Selection.getSelectionEnd(content); 229 230 if (a > b) { 231 int tmp = a; 232 a = b; 233 b = tmp; 234 } 235 236 // Skip when the selection is not yet attached. 237 if (a == -1 || b == -1) { 238 endBatchEdit(); 239 return false; 240 } 241 242 // Ignore the composing text. 243 int ca = getComposingSpanStart(content); 244 int cb = getComposingSpanEnd(content); 245 if (cb < ca) { 246 int tmp = ca; 247 ca = cb; 248 cb = tmp; 249 } 250 if (ca != -1 && cb != -1) { 251 if (ca < a) a = ca; 252 if (cb > b) b = cb; 253 } 254 255 int deleted = 0; 256 257 if (beforeLength > 0) { 258 int start = a - beforeLength; 259 if (start < 0) start = 0; 260 261 final int numDeleteBefore = a - start; 262 if (a >= 0 && numDeleteBefore > 0) { 263 content.delete(start, a); 264 deleted = numDeleteBefore; 265 } 266 } 267 268 if (afterLength > 0) { 269 b = b - deleted; 270 271 int end = b + afterLength; 272 if (end > content.length()) end = content.length(); 273 274 final int numDeleteAfter = end - b; 275 if (b >= 0 && numDeleteAfter > 0) { 276 content.delete(b, end); 277 } 278 } 279 280 endBatchEdit(); 281 282 return true; 283 } 284 285 private static int INVALID_INDEX = -1; findIndexBackward(final CharSequence cs, final int from, final int numCodePoints)286 private static int findIndexBackward(final CharSequence cs, final int from, 287 final int numCodePoints) { 288 int currentIndex = from; 289 boolean waitingHighSurrogate = false; 290 final int N = cs.length(); 291 if (currentIndex < 0 || N < currentIndex) { 292 return INVALID_INDEX; // The starting point is out of range. 293 } 294 if (numCodePoints < 0) { 295 return INVALID_INDEX; // Basically this should not happen. 296 } 297 int remainingCodePoints = numCodePoints; 298 while (true) { 299 if (remainingCodePoints == 0) { 300 return currentIndex; // Reached to the requested length in code points. 301 } 302 303 --currentIndex; 304 if (currentIndex < 0) { 305 if (waitingHighSurrogate) { 306 return INVALID_INDEX; // An invalid surrogate pair is found. 307 } 308 return 0; // Reached to the beginning of the text w/o any invalid surrogate pair. 309 } 310 final char c = cs.charAt(currentIndex); 311 if (waitingHighSurrogate) { 312 if (!java.lang.Character.isHighSurrogate(c)) { 313 return INVALID_INDEX; // An invalid surrogate pair is found. 314 } 315 waitingHighSurrogate = false; 316 --remainingCodePoints; 317 continue; 318 } 319 if (!java.lang.Character.isSurrogate(c)) { 320 --remainingCodePoints; 321 continue; 322 } 323 if (java.lang.Character.isHighSurrogate(c)) { 324 return INVALID_INDEX; // A invalid surrogate pair is found. 325 } 326 waitingHighSurrogate = true; 327 } 328 } 329 findIndexForward(final CharSequence cs, final int from, final int numCodePoints)330 private static int findIndexForward(final CharSequence cs, final int from, 331 final int numCodePoints) { 332 int currentIndex = from; 333 boolean waitingLowSurrogate = false; 334 final int N = cs.length(); 335 if (currentIndex < 0 || N < currentIndex) { 336 return INVALID_INDEX; // The starting point is out of range. 337 } 338 if (numCodePoints < 0) { 339 return INVALID_INDEX; // Basically this should not happen. 340 } 341 int remainingCodePoints = numCodePoints; 342 343 while (true) { 344 if (remainingCodePoints == 0) { 345 return currentIndex; // Reached to the requested length in code points. 346 } 347 348 if (currentIndex >= N) { 349 if (waitingLowSurrogate) { 350 return INVALID_INDEX; // An invalid surrogate pair is found. 351 } 352 return N; // Reached to the end of the text w/o any invalid surrogate pair. 353 } 354 final char c = cs.charAt(currentIndex); 355 if (waitingLowSurrogate) { 356 if (!java.lang.Character.isLowSurrogate(c)) { 357 return INVALID_INDEX; // An invalid surrogate pair is found. 358 } 359 --remainingCodePoints; 360 waitingLowSurrogate = false; 361 ++currentIndex; 362 continue; 363 } 364 if (!java.lang.Character.isSurrogate(c)) { 365 --remainingCodePoints; 366 ++currentIndex; 367 continue; 368 } 369 if (java.lang.Character.isLowSurrogate(c)) { 370 return INVALID_INDEX; // A invalid surrogate pair is found. 371 } 372 waitingLowSurrogate = true; 373 ++currentIndex; 374 } 375 } 376 377 /** 378 * The default implementation performs the deletion around the current selection position of the 379 * editable text. 380 * @param beforeLength The number of characters before the cursor to be deleted, in code points. 381 * If this is greater than the number of existing characters between the beginning of the 382 * text and the cursor, then this method does not fail but deletes all the characters in 383 * that range. 384 * @param afterLength The number of characters after the cursor to be deleted, in code points. 385 * If this is greater than the number of existing characters between the cursor and 386 * the end of the text, then this method does not fail but deletes all the characters in 387 * that range. 388 */ deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)389 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 390 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength 391 + " / " + afterLength); 392 final Editable content = getEditable(); 393 if (content == null) return false; 394 395 beginBatchEdit(); 396 397 int a = Selection.getSelectionStart(content); 398 int b = Selection.getSelectionEnd(content); 399 400 if (a > b) { 401 int tmp = a; 402 a = b; 403 b = tmp; 404 } 405 406 // Ignore the composing text. 407 int ca = getComposingSpanStart(content); 408 int cb = getComposingSpanEnd(content); 409 if (cb < ca) { 410 int tmp = ca; 411 ca = cb; 412 cb = tmp; 413 } 414 if (ca != -1 && cb != -1) { 415 if (ca < a) a = ca; 416 if (cb > b) b = cb; 417 } 418 419 if (a >= 0 && b >= 0) { 420 final int start = findIndexBackward(content, a, Math.max(beforeLength, 0)); 421 if (start != INVALID_INDEX) { 422 final int end = findIndexForward(content, b, Math.max(afterLength, 0)); 423 if (end != INVALID_INDEX) { 424 final int numDeleteBefore = a - start; 425 if (numDeleteBefore > 0) { 426 content.delete(start, a); 427 } 428 final int numDeleteAfter = end - b; 429 if (numDeleteAfter > 0) { 430 content.delete(b - numDeleteBefore, end - numDeleteBefore); 431 } 432 } 433 } 434 // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX, 435 // but the truth is that IInputConnectionWrapper running in the middle of IPC calls 436 // always returns true to the IME without waiting for the completion of this method as 437 // IInputConnectionWrapper#isAtive() returns true. This is actually why some methods 438 // including this method look like asynchronous calls from the IME. 439 } 440 441 endBatchEdit(); 442 443 return true; 444 } 445 446 /** 447 * The default implementation removes the composing state from the 448 * current editable text. In addition, only if dummy mode, a key event is 449 * sent for the new text and the current editable buffer cleared. 450 */ finishComposingText()451 public boolean finishComposingText() { 452 if (DEBUG) Log.v(TAG, "finishComposingText"); 453 final Editable content = getEditable(); 454 if (content != null) { 455 beginBatchEdit(); 456 removeComposingSpans(content); 457 // Note: sendCurrentText does nothing unless mDummyMode is set 458 sendCurrentText(); 459 endBatchEdit(); 460 } 461 return true; 462 } 463 464 /** 465 * The default implementation uses TextUtils.getCapsMode to get the 466 * cursor caps mode for the current selection position in the editable 467 * text, unless in dummy mode in which case 0 is always returned. 468 */ getCursorCapsMode(int reqModes)469 public int getCursorCapsMode(int reqModes) { 470 if (mDummyMode) return 0; 471 472 final Editable content = getEditable(); 473 if (content == null) return 0; 474 475 int a = Selection.getSelectionStart(content); 476 int b = Selection.getSelectionEnd(content); 477 478 if (a > b) { 479 int tmp = a; 480 a = b; 481 b = tmp; 482 } 483 484 return TextUtils.getCapsMode(content, a, reqModes); 485 } 486 487 /** 488 * The default implementation always returns null. 489 */ getExtractedText(ExtractedTextRequest request, int flags)490 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 491 return null; 492 } 493 494 /** 495 * The default implementation returns the given amount of text from the 496 * current cursor position in the buffer. 497 */ getTextBeforeCursor(int length, int flags)498 public CharSequence getTextBeforeCursor(int length, int flags) { 499 final Editable content = getEditable(); 500 if (content == null) return null; 501 502 int a = Selection.getSelectionStart(content); 503 int b = Selection.getSelectionEnd(content); 504 505 if (a > b) { 506 int tmp = a; 507 a = b; 508 b = tmp; 509 } 510 511 if (a <= 0) { 512 return ""; 513 } 514 515 if (length > a) { 516 length = a; 517 } 518 519 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 520 return content.subSequence(a - length, a); 521 } 522 return TextUtils.substring(content, a - length, a); 523 } 524 525 /** 526 * The default implementation returns the text currently selected, or null if none is 527 * selected. 528 */ getSelectedText(int flags)529 public CharSequence getSelectedText(int flags) { 530 final Editable content = getEditable(); 531 if (content == null) return null; 532 533 int a = Selection.getSelectionStart(content); 534 int b = Selection.getSelectionEnd(content); 535 536 if (a > b) { 537 int tmp = a; 538 a = b; 539 b = tmp; 540 } 541 542 if (a == b || a < 0) return null; 543 544 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 545 return content.subSequence(a, b); 546 } 547 return TextUtils.substring(content, a, b); 548 } 549 550 /** 551 * The default implementation returns the given amount of text from the 552 * current cursor position in the buffer. 553 */ getTextAfterCursor(int length, int flags)554 public CharSequence getTextAfterCursor(int length, int flags) { 555 final Editable content = getEditable(); 556 if (content == null) return null; 557 558 int a = Selection.getSelectionStart(content); 559 int b = Selection.getSelectionEnd(content); 560 561 if (a > b) { 562 int tmp = a; 563 a = b; 564 b = tmp; 565 } 566 567 // Guard against the case where the cursor has not been positioned yet. 568 if (b < 0) { 569 b = 0; 570 } 571 572 if (b + length > content.length()) { 573 length = content.length() - b; 574 } 575 576 577 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 578 return content.subSequence(b, b + length); 579 } 580 return TextUtils.substring(content, b, b + length); 581 } 582 583 /** 584 * The default implementation turns this into the enter key. 585 */ performEditorAction(int actionCode)586 public boolean performEditorAction(int actionCode) { 587 long eventTime = SystemClock.uptimeMillis(); 588 sendKeyEvent(new KeyEvent(eventTime, eventTime, 589 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 590 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 591 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 592 | KeyEvent.FLAG_EDITOR_ACTION)); 593 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 594 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 595 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 596 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 597 | KeyEvent.FLAG_EDITOR_ACTION)); 598 return true; 599 } 600 601 /** 602 * The default implementation does nothing. 603 */ performContextMenuAction(int id)604 public boolean performContextMenuAction(int id) { 605 return false; 606 } 607 608 /** 609 * The default implementation does nothing. 610 */ performPrivateCommand(String action, Bundle data)611 public boolean performPrivateCommand(String action, Bundle data) { 612 return false; 613 } 614 615 /** 616 * The default implementation does nothing. 617 */ requestCursorUpdates(int cursorUpdateMode)618 public boolean requestCursorUpdates(int cursorUpdateMode) { 619 return false; 620 } 621 getHandler()622 public Handler getHandler() { 623 return null; 624 } 625 626 /** 627 * The default implementation places the given text into the editable, 628 * replacing any existing composing text. The new text is marked as 629 * in a composing state with the composing style. 630 */ setComposingText(CharSequence text, int newCursorPosition)631 public boolean setComposingText(CharSequence text, int newCursorPosition) { 632 if (DEBUG) Log.v(TAG, "setComposingText " + text); 633 replaceText(text, newCursorPosition, true); 634 return true; 635 } 636 setComposingRegion(int start, int end)637 public boolean setComposingRegion(int start, int end) { 638 final Editable content = getEditable(); 639 if (content != null) { 640 beginBatchEdit(); 641 removeComposingSpans(content); 642 int a = start; 643 int b = end; 644 if (a > b) { 645 int tmp = a; 646 a = b; 647 b = tmp; 648 } 649 // Clip the end points to be within the content bounds. 650 final int length = content.length(); 651 if (a < 0) a = 0; 652 if (b < 0) b = 0; 653 if (a > length) a = length; 654 if (b > length) b = length; 655 656 ensureDefaultComposingSpans(); 657 if (mDefaultComposingSpans != null) { 658 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 659 content.setSpan(mDefaultComposingSpans[i], a, b, 660 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 661 } 662 } 663 664 content.setSpan(COMPOSING, a, b, 665 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 666 667 // Note: sendCurrentText does nothing unless mDummyMode is set 668 sendCurrentText(); 669 endBatchEdit(); 670 } 671 return true; 672 } 673 674 /** 675 * The default implementation changes the selection position in the 676 * current editable text. 677 */ setSelection(int start, int end)678 public boolean setSelection(int start, int end) { 679 if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); 680 final Editable content = getEditable(); 681 if (content == null) return false; 682 int len = content.length(); 683 if (start > len || end > len || start < 0 || end < 0) { 684 // If the given selection is out of bounds, just ignore it. 685 // Most likely the text was changed out from under the IME, 686 // and the IME is going to have to update all of its state 687 // anyway. 688 return true; 689 } 690 if (start == end && MetaKeyKeyListener.getMetaState(content, 691 MetaKeyKeyListener.META_SELECTING) != 0) { 692 // If we are in selection mode, then we want to extend the 693 // selection instead of replacing it. 694 Selection.extendSelection(content, start); 695 } else { 696 Selection.setSelection(content, start, end); 697 } 698 return true; 699 } 700 701 /** 702 * Provides standard implementation for sending a key event to the window 703 * attached to the input connection's view. 704 */ sendKeyEvent(KeyEvent event)705 public boolean sendKeyEvent(KeyEvent event) { 706 mIMM.dispatchKeyEventFromInputMethod(mTargetView, event); 707 return false; 708 } 709 710 /** 711 * Updates InputMethodManager with the current fullscreen mode. 712 */ reportFullscreenMode(boolean enabled)713 public boolean reportFullscreenMode(boolean enabled) { 714 return true; 715 } 716 sendCurrentText()717 private void sendCurrentText() { 718 if (!mDummyMode) { 719 return; 720 } 721 722 Editable content = getEditable(); 723 if (content != null) { 724 final int N = content.length(); 725 if (N == 0) { 726 return; 727 } 728 if (N == 1) { 729 // If it's 1 character, we have a chance of being 730 // able to generate normal key events... 731 if (mKeyCharacterMap == null) { 732 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 733 } 734 char[] chars = new char[1]; 735 content.getChars(0, 1, chars, 0); 736 KeyEvent[] events = mKeyCharacterMap.getEvents(chars); 737 if (events != null) { 738 for (int i=0; i<events.length; i++) { 739 if (DEBUG) Log.v(TAG, "Sending: " + events[i]); 740 sendKeyEvent(events[i]); 741 } 742 content.clear(); 743 return; 744 } 745 } 746 747 // Otherwise, revert to the special key event containing 748 // the actual characters. 749 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), 750 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0); 751 sendKeyEvent(event); 752 content.clear(); 753 } 754 } 755 ensureDefaultComposingSpans()756 private void ensureDefaultComposingSpans() { 757 if (mDefaultComposingSpans == null) { 758 Context context; 759 if (mTargetView != null) { 760 context = mTargetView.getContext(); 761 } else if (mIMM.mCurRootView != null) { 762 final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView(); 763 context = servedView != null ? servedView.getContext() : null; 764 } else { 765 context = null; 766 } 767 if (context != null) { 768 TypedArray ta = context.getTheme() 769 .obtainStyledAttributes(new int[] { 770 com.android.internal.R.attr.candidatesTextStyleSpans 771 }); 772 CharSequence style = ta.getText(0); 773 ta.recycle(); 774 if (style != null && style instanceof Spanned) { 775 mDefaultComposingSpans = ((Spanned)style).getSpans( 776 0, style.length(), Object.class); 777 } 778 } 779 } 780 } 781 replaceText(CharSequence text, int newCursorPosition, boolean composing)782 private void replaceText(CharSequence text, int newCursorPosition, 783 boolean composing) { 784 final Editable content = getEditable(); 785 if (content == null) { 786 return; 787 } 788 789 beginBatchEdit(); 790 791 // delete composing text set previously. 792 int a = getComposingSpanStart(content); 793 int b = getComposingSpanEnd(content); 794 795 if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b); 796 797 if (b < a) { 798 int tmp = a; 799 a = b; 800 b = tmp; 801 } 802 803 if (a != -1 && b != -1) { 804 removeComposingSpans(content); 805 } else { 806 a = Selection.getSelectionStart(content); 807 b = Selection.getSelectionEnd(content); 808 if (a < 0) a = 0; 809 if (b < 0) b = 0; 810 if (b < a) { 811 int tmp = a; 812 a = b; 813 b = tmp; 814 } 815 } 816 817 if (composing) { 818 Spannable sp = null; 819 if (!(text instanceof Spannable)) { 820 sp = new SpannableStringBuilder(text); 821 text = sp; 822 ensureDefaultComposingSpans(); 823 if (mDefaultComposingSpans != null) { 824 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 825 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(), 826 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 827 } 828 } 829 } else { 830 sp = (Spannable)text; 831 } 832 setComposingSpans(sp); 833 } 834 835 if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \"" 836 + text + "\", composing=" + composing 837 + ", type=" + text.getClass().getCanonicalName()); 838 839 if (DEBUG) { 840 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 841 lp.println("Current text:"); 842 TextUtils.dumpSpans(content, lp, " "); 843 lp.println("Composing text:"); 844 TextUtils.dumpSpans(text, lp, " "); 845 } 846 847 // Position the cursor appropriately, so that after replacing the 848 // desired range of text it will be located in the correct spot. 849 // This allows us to deal with filters performing edits on the text 850 // we are providing here. 851 if (newCursorPosition > 0) { 852 newCursorPosition += b - 1; 853 } else { 854 newCursorPosition += a; 855 } 856 if (newCursorPosition < 0) newCursorPosition = 0; 857 if (newCursorPosition > content.length()) 858 newCursorPosition = content.length(); 859 Selection.setSelection(content, newCursorPosition); 860 861 content.replace(a, b, text); 862 863 if (DEBUG) { 864 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 865 lp.println("Final text:"); 866 TextUtils.dumpSpans(content, lp, " "); 867 } 868 869 endBatchEdit(); 870 } 871 872 /** 873 * The default implementation does nothing. 874 */ commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)875 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { 876 return false; 877 } 878 } 879