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