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