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