BaseInputConnection.java revision e811de2d6cd736b36a2a2ff552e6893a4a021045
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package android.view.inputmethod; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.os.Bundle; 22import android.os.SystemClock; 23import android.text.Editable; 24import android.text.NoCopySpan; 25import android.text.Selection; 26import android.text.Spannable; 27import android.text.SpannableStringBuilder; 28import android.text.Spanned; 29import android.text.TextUtils; 30import android.text.method.MetaKeyKeyListener; 31import android.util.Log; 32import android.util.LogPrinter; 33import android.view.KeyCharacterMap; 34import android.view.KeyEvent; 35import android.view.View; 36import android.view.ViewRootImpl; 37 38class 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 */ 48public 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 63 BaseInputConnection(InputMethodManager mgr, boolean fullEditor) { 64 mIMM = mgr; 65 mTargetView = null; 66 mDummyMode = !fullEditor; 67 } 68 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 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 89 public static void setComposingSpans(Spannable text) { 90 setComposingSpans(text, 0, text.length()); 91 } 92 93 /** @hide */ 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 119 public static int getComposingSpanStart(Spannable text) { 120 return text.getSpanStart(COMPOSING); 121 } 122 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 */ 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 */ 144 public boolean beginBatchEdit() { 145 return false; 146 } 147 148 /** 149 * Default implementation does nothing. 150 */ 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 */ 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 */ 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 */ 179 public boolean commitCompletion(CompletionInfo text) { 180 return false; 181 } 182 183 /** 184 * Default implementation does nothing and returns false. 185 */ 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 */ 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 204 * selection position of the editable text. 205 * @param beforeLength 206 * @param afterLength 207 */ 208 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 209 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength 210 + " / " + afterLength); 211 final Editable content = getEditable(); 212 if (content == null) return false; 213 214 beginBatchEdit(); 215 216 int a = Selection.getSelectionStart(content); 217 int b = Selection.getSelectionEnd(content); 218 219 if (a > b) { 220 int tmp = a; 221 a = b; 222 b = tmp; 223 } 224 225 // ignore the composing text. 226 int ca = getComposingSpanStart(content); 227 int cb = getComposingSpanEnd(content); 228 if (cb < ca) { 229 int tmp = ca; 230 ca = cb; 231 cb = tmp; 232 } 233 if (ca != -1 && cb != -1) { 234 if (ca < a) a = ca; 235 if (cb > b) b = cb; 236 } 237 238 int deleted = 0; 239 240 if (beforeLength > 0) { 241 int start = a - beforeLength; 242 if (start < 0) start = 0; 243 content.delete(start, a); 244 deleted = a - start; 245 } 246 247 if (afterLength > 0) { 248 b = b - deleted; 249 250 int end = b + afterLength; 251 if (end > content.length()) end = content.length(); 252 253 content.delete(b, end); 254 } 255 256 endBatchEdit(); 257 258 return true; 259 } 260 261 /** 262 * The default implementation removes the composing state from the 263 * current editable text. In addition, only if dummy mode, a key event is 264 * sent for the new text and the current editable buffer cleared. 265 */ 266 public boolean finishComposingText() { 267 if (DEBUG) Log.v(TAG, "finishComposingText"); 268 final Editable content = getEditable(); 269 if (content != null) { 270 beginBatchEdit(); 271 removeComposingSpans(content); 272 endBatchEdit(); 273 sendCurrentText(); 274 } 275 return true; 276 } 277 278 /** 279 * The default implementation uses TextUtils.getCapsMode to get the 280 * cursor caps mode for the current selection position in the editable 281 * text, unless in dummy mode in which case 0 is always returned. 282 */ 283 public int getCursorCapsMode(int reqModes) { 284 if (mDummyMode) return 0; 285 286 final Editable content = getEditable(); 287 if (content == null) return 0; 288 289 int a = Selection.getSelectionStart(content); 290 int b = Selection.getSelectionEnd(content); 291 292 if (a > b) { 293 int tmp = a; 294 a = b; 295 b = tmp; 296 } 297 298 return TextUtils.getCapsMode(content, a, reqModes); 299 } 300 301 /** 302 * The default implementation always returns null. 303 */ 304 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 305 return null; 306 } 307 308 /** 309 * The default implementation returns the given amount of text from the 310 * current cursor position in the buffer. 311 */ 312 public CharSequence getTextBeforeCursor(int length, int flags) { 313 final Editable content = getEditable(); 314 if (content == null) return null; 315 316 int a = Selection.getSelectionStart(content); 317 int b = Selection.getSelectionEnd(content); 318 319 if (a > b) { 320 int tmp = a; 321 a = b; 322 b = tmp; 323 } 324 325 if (a <= 0) { 326 return ""; 327 } 328 329 if (length > a) { 330 length = a; 331 } 332 333 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 334 return content.subSequence(a - length, a); 335 } 336 return TextUtils.substring(content, a - length, a); 337 } 338 339 /** 340 * The default implementation returns the text currently selected, or null if none is 341 * selected. 342 */ 343 public CharSequence getSelectedText(int flags) { 344 final Editable content = getEditable(); 345 if (content == null) return null; 346 347 int a = Selection.getSelectionStart(content); 348 int b = Selection.getSelectionEnd(content); 349 350 if (a > b) { 351 int tmp = a; 352 a = b; 353 b = tmp; 354 } 355 356 if (a == b) return null; 357 358 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 359 return content.subSequence(a, b); 360 } 361 return TextUtils.substring(content, a, b); 362 } 363 364 /** 365 * The default implementation returns the given amount of text from the 366 * current cursor position in the buffer. 367 */ 368 public CharSequence getTextAfterCursor(int length, int flags) { 369 final Editable content = getEditable(); 370 if (content == null) return null; 371 372 int a = Selection.getSelectionStart(content); 373 int b = Selection.getSelectionEnd(content); 374 375 if (a > b) { 376 int tmp = a; 377 a = b; 378 b = tmp; 379 } 380 381 // Guard against the case where the cursor has not been positioned yet. 382 if (b < 0) { 383 b = 0; 384 } 385 386 if (b + length > content.length()) { 387 length = content.length() - b; 388 } 389 390 391 if ((flags&GET_TEXT_WITH_STYLES) != 0) { 392 return content.subSequence(b, b + length); 393 } 394 return TextUtils.substring(content, b, b + length); 395 } 396 397 /** 398 * The default implementation turns this into the enter key. 399 */ 400 public boolean performEditorAction(int actionCode) { 401 long eventTime = SystemClock.uptimeMillis(); 402 sendKeyEvent(new KeyEvent(eventTime, eventTime, 403 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 404 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 405 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 406 | KeyEvent.FLAG_EDITOR_ACTION)); 407 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 408 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 409 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 410 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 411 | KeyEvent.FLAG_EDITOR_ACTION)); 412 return true; 413 } 414 415 /** 416 * The default implementation does nothing. 417 */ 418 public boolean performContextMenuAction(int id) { 419 return false; 420 } 421 422 /** 423 * The default implementation does nothing. 424 */ 425 public boolean performPrivateCommand(String action, Bundle data) { 426 return false; 427 } 428 429 /** 430 * The default implementation places the given text into the editable, 431 * replacing any existing composing text. The new text is marked as 432 * in a composing state with the composing style. 433 */ 434 public boolean setComposingText(CharSequence text, int newCursorPosition) { 435 if (DEBUG) Log.v(TAG, "setComposingText " + text); 436 replaceText(text, newCursorPosition, true); 437 return true; 438 } 439 440 public boolean setComposingRegion(int start, int end) { 441 final Editable content = getEditable(); 442 if (content != null) { 443 beginBatchEdit(); 444 removeComposingSpans(content); 445 int a = start; 446 int b = end; 447 if (a > b) { 448 int tmp = a; 449 a = b; 450 b = tmp; 451 } 452 // Clip the end points to be within the content bounds. 453 final int length = content.length(); 454 if (a < 0) a = 0; 455 if (b < 0) b = 0; 456 if (a > length) a = length; 457 if (b > length) b = length; 458 459 ensureDefaultComposingSpans(); 460 if (mDefaultComposingSpans != null) { 461 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 462 content.setSpan(mDefaultComposingSpans[i], a, b, 463 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 464 } 465 } 466 467 content.setSpan(COMPOSING, a, b, 468 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 469 470 endBatchEdit(); 471 sendCurrentText(); 472 } 473 return true; 474 } 475 476 /** 477 * The default implementation changes the selection position in the 478 * current editable text. 479 */ 480 public boolean setSelection(int start, int end) { 481 if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); 482 final Editable content = getEditable(); 483 if (content == null) return false; 484 int len = content.length(); 485 if (start > len || end > len) { 486 // If the given selection is out of bounds, just ignore it. 487 // Most likely the text was changed out from under the IME, 488 // the the IME is going to have to update all of its state 489 // anyway. 490 return true; 491 } 492 if (start == end && MetaKeyKeyListener.getMetaState(content, 493 MetaKeyKeyListener.META_SELECTING) != 0) { 494 // If we are in selection mode, then we want to extend the 495 // selection instead of replacing it. 496 Selection.extendSelection(content, start); 497 } else { 498 Selection.setSelection(content, start, end); 499 } 500 return true; 501 } 502 503 /** 504 * Provides standard implementation for sending a key event to the window 505 * attached to the input connection's view. 506 */ 507 public boolean sendKeyEvent(KeyEvent event) { 508 synchronized (mIMM.mH) { 509 ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null; 510 if (viewRootImpl == null) { 511 if (mIMM.mServedView != null) { 512 viewRootImpl = mIMM.mServedView.getViewRootImpl(); 513 } 514 } 515 if (viewRootImpl != null) { 516 viewRootImpl.dispatchKeyFromIme(event); 517 } 518 } 519 return false; 520 } 521 522 /** 523 * Updates InputMethodManager with the current fullscreen mode. 524 */ 525 public boolean reportFullscreenMode(boolean enabled) { 526 mIMM.setFullscreenMode(enabled); 527 return true; 528 } 529 530 private void sendCurrentText() { 531 if (!mDummyMode) { 532 return; 533 } 534 535 Editable content = getEditable(); 536 if (content != null) { 537 final int N = content.length(); 538 if (N == 0) { 539 return; 540 } 541 if (N == 1) { 542 // If it's 1 character, we have a chance of being 543 // able to generate normal key events... 544 if (mKeyCharacterMap == null) { 545 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 546 } 547 char[] chars = new char[1]; 548 content.getChars(0, 1, chars, 0); 549 KeyEvent[] events = mKeyCharacterMap.getEvents(chars); 550 if (events != null) { 551 for (int i=0; i<events.length; i++) { 552 if (DEBUG) Log.v(TAG, "Sending: " + events[i]); 553 sendKeyEvent(events[i]); 554 } 555 content.clear(); 556 return; 557 } 558 } 559 560 // Otherwise, revert to the special key event containing 561 // the actual characters. 562 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), 563 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0); 564 sendKeyEvent(event); 565 content.clear(); 566 } 567 } 568 569 private void ensureDefaultComposingSpans() { 570 if (mDefaultComposingSpans == null) { 571 Context context; 572 if (mTargetView != null) { 573 context = mTargetView.getContext(); 574 } else if (mIMM.mServedView != null) { 575 context = mIMM.mServedView.getContext(); 576 } else { 577 context = null; 578 } 579 if (context != null) { 580 TypedArray ta = context.getTheme() 581 .obtainStyledAttributes(new int[] { 582 com.android.internal.R.attr.candidatesTextStyleSpans 583 }); 584 CharSequence style = ta.getText(0); 585 ta.recycle(); 586 if (style != null && style instanceof Spanned) { 587 mDefaultComposingSpans = ((Spanned)style).getSpans( 588 0, style.length(), Object.class); 589 } 590 } 591 } 592 } 593 594 private void replaceText(CharSequence text, int newCursorPosition, 595 boolean composing) { 596 final Editable content = getEditable(); 597 if (content == null) { 598 return; 599 } 600 601 beginBatchEdit(); 602 603 // delete composing text set previously. 604 int a = getComposingSpanStart(content); 605 int b = getComposingSpanEnd(content); 606 607 if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b); 608 609 if (b < a) { 610 int tmp = a; 611 a = b; 612 b = tmp; 613 } 614 615 if (a != -1 && b != -1) { 616 removeComposingSpans(content); 617 } else { 618 a = Selection.getSelectionStart(content); 619 b = Selection.getSelectionEnd(content); 620 if (a < 0) a = 0; 621 if (b < 0) b = 0; 622 if (b < a) { 623 int tmp = a; 624 a = b; 625 b = tmp; 626 } 627 } 628 629 if (composing) { 630 Spannable sp = null; 631 if (!(text instanceof Spannable)) { 632 sp = new SpannableStringBuilder(text); 633 text = sp; 634 ensureDefaultComposingSpans(); 635 if (mDefaultComposingSpans != null) { 636 for (int i = 0; i < mDefaultComposingSpans.length; ++i) { 637 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(), 638 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); 639 } 640 } 641 } else { 642 sp = (Spannable)text; 643 } 644 setComposingSpans(sp); 645 } 646 647 if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \"" 648 + text + "\", composing=" + composing 649 + ", type=" + text.getClass().getCanonicalName()); 650 651 if (DEBUG) { 652 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 653 lp.println("Current text:"); 654 TextUtils.dumpSpans(content, lp, " "); 655 lp.println("Composing text:"); 656 TextUtils.dumpSpans(text, lp, " "); 657 } 658 659 // Position the cursor appropriately, so that after replacing the 660 // desired range of text it will be located in the correct spot. 661 // This allows us to deal with filters performing edits on the text 662 // we are providing here. 663 if (newCursorPosition > 0) { 664 newCursorPosition += b - 1; 665 } else { 666 newCursorPosition += a; 667 } 668 if (newCursorPosition < 0) newCursorPosition = 0; 669 if (newCursorPosition > content.length()) 670 newCursorPosition = content.length(); 671 Selection.setSelection(content, newCursorPosition); 672 673 content.replace(a, b, text); 674 675 if (DEBUG) { 676 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); 677 lp.println("Final text:"); 678 TextUtils.dumpSpans(content, lp, " "); 679 } 680 681 endBatchEdit(); 682 } 683} 684