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