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.content.Context; 20 import android.content.res.TypedArray; 21 import android.os.Bundle; 22 import android.os.SystemClock; 23 import android.text.Editable; 24 import android.text.NoCopySpan; 25 import android.text.Selection; 26 import android.text.Spannable; 27 import android.text.SpannableStringBuilder; 28 import android.text.Spanned; 29 import android.text.TextUtils; 30 import android.text.method.MetaKeyKeyListener; 31 import android.util.Log; 32 import android.util.LogPrinter; 33 import android.view.KeyCharacterMap; 34 import android.view.KeyEvent; 35 import android.view.View; 36 import android.view.ViewRootImpl; 37 38 class ComposingText implements NoCopySpan { 39 } 40 41 /** 42 * Base class for implementors of the InputConnection interface, taking care 43 * of most of the common behavior for providing a connection to an Editable. 44 * Implementors of this class will want to be sure to implement 45 * {@link #getEditable} to provide access to their own editable object, and 46 * to refer to the documentation in {@link InputConnection}. 47 */ 48 public class BaseInputConnection implements InputConnection { 49 private static final boolean DEBUG = false; 50 private static final String TAG = "BaseInputConnection"; 51 static final Object COMPOSING = new ComposingText(); 52 53 /** @hide */ 54 protected final InputMethodManager mIMM; 55 final View mTargetView; 56 final boolean mDummyMode; 57 58 private Object[] mDefaultComposingSpans; 59 60 Editable mEditable; 61 KeyCharacterMap mKeyCharacterMap; 62 BaseInputConnection(InputMethodManager mgr, boolean fullEditor)63 BaseInputConnection(InputMethodManager mgr, boolean fullEditor) { 64 mIMM = mgr; 65 mTargetView = null; 66 mDummyMode = !fullEditor; 67 } 68 BaseInputConnection(View targetView, boolean fullEditor)69 public BaseInputConnection(View targetView, boolean fullEditor) { 70 mIMM = (InputMethodManager)targetView.getContext().getSystemService( 71 Context.INPUT_METHOD_SERVICE); 72 mTargetView = targetView; 73 mDummyMode = !fullEditor; 74 } 75 removeComposingSpans(Spannable text)76 public static final void removeComposingSpans(Spannable text) { 77 text.removeSpan(COMPOSING); 78 Object[] sps = text.getSpans(0, text.length(), Object.class); 79 if (sps != null) { 80 for (int i=sps.length-1; i>=0; i--) { 81 Object o = sps[i]; 82 if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { 83 text.removeSpan(o); 84 } 85 } 86 } 87 } 88 setComposingSpans(Spannable text)89 public static void setComposingSpans(Spannable text) { 90 setComposingSpans(text, 0, text.length()); 91 } 92 93 /** @hide */ setComposingSpans(Spannable text, int start, int end)94 public static void setComposingSpans(Spannable text, int start, int end) { 95 final Object[] sps = text.getSpans(start, end, Object.class); 96 if (sps != null) { 97 for (int i=sps.length-1; i>=0; i--) { 98 final Object o = sps[i]; 99 if (o == COMPOSING) { 100 text.removeSpan(o); 101 continue; 102 } 103 104 final int fl = text.getSpanFlags(o); 105 if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) 106 != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { 107 text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), 108 (fl & ~Spanned.SPAN_POINT_MARK_MASK) 109 | Spanned.SPAN_COMPOSING 110 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 111 } 112 } 113 } 114 115 text.setSpan(COMPOSING, start, end, 116 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 117 } 118 getComposingSpanStart(Spannable text)119 public static int getComposingSpanStart(Spannable text) { 120 return text.getSpanStart(COMPOSING); 121 } 122 getComposingSpanEnd(Spannable text)123 public static int getComposingSpanEnd(Spannable text) { 124 return text.getSpanEnd(COMPOSING); 125 } 126 127 /** 128 * Return the target of edit operations. The default implementation 129 * returns its own fake editable that is just used for composing text; 130 * subclasses that are real text editors should override this and 131 * supply their own. 132 */ getEditable()133 public Editable getEditable() { 134 if (mEditable == null) { 135 mEditable = Editable.Factory.getInstance().newEditable(""); 136 Selection.setSelection(mEditable, 0); 137 } 138 return mEditable; 139 } 140 141 /** 142 * Default implementation does nothing. 143 */ beginBatchEdit()144 public boolean beginBatchEdit() { 145 return false; 146 } 147 148 /** 149 * Default implementation does nothing. 150 */ endBatchEdit()151 public boolean endBatchEdit() { 152 return false; 153 } 154 155 /** 156 * Called when this InputConnection is no longer used by the InputMethodManager. 157 * 158 * @hide 159 */ reportFinish()160 protected void reportFinish() { 161 // Intentionaly empty 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 mIMM.notifyUserAction(); 199 sendCurrentText(); 200 return true; 201 } 202 203 /** 204 * The default implementation performs the deletion around the current 205 * selection position of the editable text. 206 * @param beforeLength 207 * @param afterLength 208 */ deleteSurroundingText(int beforeLength, int afterLength)209 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 210 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength 211 + " / " + afterLength); 212 final Editable content = getEditable(); 213 if (content == null) return false; 214 215 beginBatchEdit(); 216 217 int a = Selection.getSelectionStart(content); 218 int b = Selection.getSelectionEnd(content); 219 220 if (a > b) { 221 int tmp = a; 222 a = b; 223 b = tmp; 224 } 225 226 // ignore the composing text. 227 int ca = getComposingSpanStart(content); 228 int cb = getComposingSpanEnd(content); 229 if (cb < ca) { 230 int tmp = ca; 231 ca = cb; 232 cb = tmp; 233 } 234 if (ca != -1 && cb != -1) { 235 if (ca < a) a = ca; 236 if (cb > b) b = cb; 237 } 238 239 int deleted = 0; 240 241 if (beforeLength > 0) { 242 int start = a - beforeLength; 243 if (start < 0) start = 0; 244 content.delete(start, a); 245 deleted = a - start; 246 } 247 248 if (afterLength > 0) { 249 b = b - deleted; 250 251 int end = b + afterLength; 252 if (end > content.length()) end = content.length(); 253 254 content.delete(b, end); 255 } 256 257 endBatchEdit(); 258 259 return true; 260 } 261 262 /** 263 * The default implementation removes the composing state from the 264 * current editable text. In addition, only if dummy mode, a key event is 265 * sent for the new text and the current editable buffer cleared. 266 */ finishComposingText()267 public boolean finishComposingText() { 268 if (DEBUG) Log.v(TAG, "finishComposingText"); 269 final Editable content = getEditable(); 270 if (content != null) { 271 beginBatchEdit(); 272 removeComposingSpans(content); 273 // Note: sendCurrentText does nothing unless mDummyMode is set 274 sendCurrentText(); 275 endBatchEdit(); 276 } 277 return true; 278 } 279 280 /** 281 * The default implementation uses TextUtils.getCapsMode to get the 282 * cursor caps mode for the current selection position in the editable 283 * text, unless in dummy mode in which case 0 is always returned. 284 */ getCursorCapsMode(int reqModes)285 public int getCursorCapsMode(int reqModes) { 286 if (mDummyMode) return 0; 287 288 final Editable content = getEditable(); 289 if (content == null) return 0; 290 291 int a = Selection.getSelectionStart(content); 292 int b = Selection.getSelectionEnd(content); 293 294 if (a > b) { 295 int tmp = a; 296 a = b; 297 b = tmp; 298 } 299 300 return TextUtils.getCapsMode(content, a, reqModes); 301 } 302 303 /** 304 * The default implementation always returns null. 305 */ getExtractedText(ExtractedTextRequest request, int flags)306 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 307 return null; 308 } 309 310 /** 311 * The default implementation returns the given amount of text from the 312 * current cursor position in the buffer. 313 */ getTextBeforeCursor(int length, int flags)314 public CharSequence getTextBeforeCursor(int length, int flags) { 315 final Editable content = getEditable(); 316 if (content == null) return null; 317 318 int a = Selection.getSelectionStart(content); 319 int b = Selection.getSelectionEnd(content); 320 321 if (a > b) { 322 int tmp = a; 323 a = b; 324 b = tmp; 325 } 326 327 if (a <= 0) { 328 return ""; 329 } 330 331 if (length > a) { 332 length = a; 333 } 334 335 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 336 return content.subSequence(a - length, a); 337 } 338 return TextUtils.substring(content, a - length, a); 339 } 340 341 /** 342 * The default implementation returns the text currently selected, or null if none is 343 * selected. 344 */ getSelectedText(int flags)345 public CharSequence getSelectedText(int flags) { 346 final Editable content = getEditable(); 347 if (content == null) return null; 348 349 int a = Selection.getSelectionStart(content); 350 int b = Selection.getSelectionEnd(content); 351 352 if (a > b) { 353 int tmp = a; 354 a = b; 355 b = tmp; 356 } 357 358 if (a == b) return null; 359 360 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 361 return content.subSequence(a, b); 362 } 363 return TextUtils.substring(content, a, b); 364 } 365 366 /** 367 * The default implementation returns the given amount of text from the 368 * current cursor position in the buffer. 369 */ getTextAfterCursor(int length, int flags)370 public CharSequence getTextAfterCursor(int length, int flags) { 371 final Editable content = getEditable(); 372 if (content == null) return null; 373 374 int a = Selection.getSelectionStart(content); 375 int b = Selection.getSelectionEnd(content); 376 377 if (a > b) { 378 int tmp = a; 379 a = b; 380 b = tmp; 381 } 382 383 // Guard against the case where the cursor has not been positioned yet. 384 if (b < 0) { 385 b = 0; 386 } 387 388 if (b + length > content.length()) { 389 length = content.length() - b; 390 } 391 392 393 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 394 return content.subSequence(b, b + length); 395 } 396 return TextUtils.substring(content, b, b + length); 397 } 398 399 /** 400 * The default implementation turns this into the enter key. 401 */ performEditorAction(int actionCode)402 public boolean performEditorAction(int actionCode) { 403 long eventTime = SystemClock.uptimeMillis(); 404 sendKeyEvent(new KeyEvent(eventTime, eventTime, 405 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 406 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 407 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 408 | KeyEvent.FLAG_EDITOR_ACTION)); 409 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 410 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 411 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 412 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 413 | KeyEvent.FLAG_EDITOR_ACTION)); 414 return true; 415 } 416 417 /** 418 * The default implementation does nothing. 419 */ performContextMenuAction(int id)420 public boolean performContextMenuAction(int id) { 421 return false; 422 } 423 424 /** 425 * The default implementation does nothing. 426 */ performPrivateCommand(String action, Bundle data)427 public boolean performPrivateCommand(String action, Bundle data) { 428 return false; 429 } 430 431 /** 432 * The default implementation does nothing. 433 */ requestCursorUpdates(int cursorUpdateMode)434 public boolean requestCursorUpdates(int cursorUpdateMode) { 435 return false; 436 } 437 438 /** 439 * The default implementation places the given text into the editable, 440 * replacing any existing composing text. The new text is marked as 441 * in a composing state with the composing style. 442 */ setComposingText(CharSequence text, int newCursorPosition)443 public boolean setComposingText(CharSequence text, int newCursorPosition) { 444 if (DEBUG) Log.v(TAG, "setComposingText " + text); 445 replaceText(text, newCursorPosition, true); 446 mIMM.notifyUserAction(); 447 return true; 448 } 449 setComposingRegion(int start, int end)450 public boolean setComposingRegion(int start, int end) { 451 final Editable content = getEditable(); 452 if (content != null) { 453 beginBatchEdit(); 454 removeComposingSpans(content); 455 int a = start; 456 int b = end; 457 if (a > b) { 458 int tmp = a; 459 a = b; 460 b = tmp; 461 } 462 // Clip the end points to be within the content bounds. 463 final int length = content.length(); 464 if (a < 0) a = 0; 465 if (b < 0) b = 0; 466 if (a > length) a = length; 467 if (b > length) b = length; 468 469 ensureDefaultComposingSpans(); 470 if (mDefaultComposingSpans != null) { 471 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 472 content.setSpan(mDefaultComposingSpans[i], a, b, 473 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 474 } 475 } 476 477 content.setSpan(COMPOSING, a, b, 478 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 479 480 // Note: sendCurrentText does nothing unless mDummyMode is set 481 sendCurrentText(); 482 endBatchEdit(); 483 } 484 return true; 485 } 486 487 /** 488 * The default implementation changes the selection position in the 489 * current editable text. 490 */ setSelection(int start, int end)491 public boolean setSelection(int start, int end) { 492 if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); 493 final Editable content = getEditable(); 494 if (content == null) return false; 495 int len = content.length(); 496 if (start > len || end > len || start < 0 || end < 0) { 497 // If the given selection is out of bounds, just ignore it. 498 // Most likely the text was changed out from under the IME, 499 // and the IME is going to have to update all of its state 500 // anyway. 501 return true; 502 } 503 if (start == end && MetaKeyKeyListener.getMetaState(content, 504 MetaKeyKeyListener.META_SELECTING) != 0) { 505 // If we are in selection mode, then we want to extend the 506 // selection instead of replacing it. 507 Selection.extendSelection(content, start); 508 } else { 509 Selection.setSelection(content, start, end); 510 } 511 return true; 512 } 513 514 /** 515 * Provides standard implementation for sending a key event to the window 516 * attached to the input connection's view. 517 */ sendKeyEvent(KeyEvent event)518 public boolean sendKeyEvent(KeyEvent event) { 519 synchronized (mIMM.mH) { 520 ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null; 521 if (viewRootImpl == null) { 522 if (mIMM.mServedView != null) { 523 viewRootImpl = mIMM.mServedView.getViewRootImpl(); 524 } 525 } 526 if (viewRootImpl != null) { 527 viewRootImpl.dispatchKeyFromIme(event); 528 } 529 } 530 mIMM.notifyUserAction(); 531 return false; 532 } 533 534 /** 535 * Updates InputMethodManager with the current fullscreen mode. 536 */ reportFullscreenMode(boolean enabled)537 public boolean reportFullscreenMode(boolean enabled) { 538 mIMM.setFullscreenMode(enabled); 539 return true; 540 } 541 sendCurrentText()542 private void sendCurrentText() { 543 if (!mDummyMode) { 544 return; 545 } 546 547 Editable content = getEditable(); 548 if (content != null) { 549 final int N = content.length(); 550 if (N == 0) { 551 return; 552 } 553 if (N == 1) { 554 // If it's 1 character, we have a chance of being 555 // able to generate normal key events... 556 if (mKeyCharacterMap == null) { 557 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 558 } 559 char[] chars = new char[1]; 560 content.getChars(0, 1, chars, 0); 561 KeyEvent[] events = mKeyCharacterMap.getEvents(chars); 562 if (events != null) { 563 for (int i=0; i<events.length; i++) { 564 if (DEBUG) Log.v(TAG, "Sending: " + events[i]); 565 sendKeyEvent(events[i]); 566 } 567 content.clear(); 568 return; 569 } 570 } 571 572 // Otherwise, revert to the special key event containing 573 // the actual characters. 574 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), 575 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0); 576 sendKeyEvent(event); 577 content.clear(); 578 } 579 } 580 ensureDefaultComposingSpans()581 private void ensureDefaultComposingSpans() { 582 if (mDefaultComposingSpans == null) { 583 Context context; 584 if (mTargetView != null) { 585 context = mTargetView.getContext(); 586 } else if (mIMM.mServedView != null) { 587 context = mIMM.mServedView.getContext(); 588 } else { 589 context = null; 590 } 591 if (context != null) { 592 TypedArray ta = context.getTheme() 593 .obtainStyledAttributes(new int[] { 594 com.android.internal.R.attr.candidatesTextStyleSpans 595 }); 596 CharSequence style = ta.getText(0); 597 ta.recycle(); 598 if (style != null && style instanceof Spanned) { 599 mDefaultComposingSpans = ((Spanned)style).getSpans( 600 0, style.length(), Object.class); 601 } 602 } 603 } 604 } 605 replaceText(CharSequence text, int newCursorPosition, boolean composing)606 private void replaceText(CharSequence text, int newCursorPosition, 607 boolean composing) { 608 final Editable content = getEditable(); 609 if (content == null) { 610 return; 611 } 612 613 beginBatchEdit(); 614 615 // delete composing text set previously. 616 int a = getComposingSpanStart(content); 617 int b = getComposingSpanEnd(content); 618 619 if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b); 620 621 if (b < a) { 622 int tmp = a; 623 a = b; 624 b = tmp; 625 } 626 627 if (a != -1 && b != -1) { 628 removeComposingSpans(content); 629 } else { 630 a = Selection.getSelectionStart(content); 631 b = Selection.getSelectionEnd(content); 632 if (a < 0) a = 0; 633 if (b < 0) b = 0; 634 if (b < a) { 635 int tmp = a; 636 a = b; 637 b = tmp; 638 } 639 } 640 641 if (composing) { 642 Spannable sp = null; 643 if (!(text instanceof Spannable)) { 644 sp = new SpannableStringBuilder(text); 645 text = sp; 646 ensureDefaultComposingSpans(); 647 if (mDefaultComposingSpans != null) { 648 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 649 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(), 650 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 651 } 652 } 653 } else { 654 sp = (Spannable)text; 655 } 656 setComposingSpans(sp); 657 } 658 659 if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \"" 660 + text + "\", composing=" + composing 661 + ", type=" + text.getClass().getCanonicalName()); 662 663 if (DEBUG) { 664 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 665 lp.println("Current text:"); 666 TextUtils.dumpSpans(content, lp, " "); 667 lp.println("Composing text:"); 668 TextUtils.dumpSpans(text, lp, " "); 669 } 670 671 // Position the cursor appropriately, so that after replacing the 672 // desired range of text it will be located in the correct spot. 673 // This allows us to deal with filters performing edits on the text 674 // we are providing here. 675 if (newCursorPosition > 0) { 676 newCursorPosition += b - 1; 677 } else { 678 newCursorPosition += a; 679 } 680 if (newCursorPosition < 0) newCursorPosition = 0; 681 if (newCursorPosition > content.length()) 682 newCursorPosition = content.length(); 683 Selection.setSelection(content, newCursorPosition); 684 685 content.replace(a, b, text); 686 687 if (DEBUG) { 688 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 689 lp.println("Final text:"); 690 TextUtils.dumpSpans(content, lp, " "); 691 } 692 693 endBatchEdit(); 694 } 695 } 696