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