blob: d6b973e745bed4687b56a80639242b736ddc1c36 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import 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;
Dianne Hackborn6dd005b2011-07-18 13:22:50 -070036import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037
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
Jean Chalarde811de22013-05-24 08:06:28 +090045 * {@link #getEditable} to provide access to their own editable object, and
46 * to refer to the documentation in {@link InputConnection}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 */
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();
satokf9f01002011-05-19 21:31:50 +090052
53 /** @hide */
54 protected final InputMethodManager mIMM;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 final View mTargetView;
56 final boolean mDummyMode;
57
58 private Object[] mDefaultComposingSpans;
59
60 Editable mEditable;
61 KeyCharacterMap mKeyCharacterMap;
62
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070063 BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 mIMM = mgr;
65 mTargetView = null;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070066 mDummyMode = !fullEditor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 }
68
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070069 public BaseInputConnection(View targetView, boolean fullEditor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 mIMM = (InputMethodManager)targetView.getContext().getSystemService(
71 Context.INPUT_METHOD_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 mTargetView = targetView;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070073 mDummyMode = !fullEditor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 }
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 }
Amith Yamasania90b7f02010-08-25 18:27:20 -070088
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 public static void setComposingSpans(Spannable text) {
Amith Yamasania90b7f02010-08-25 18:27:20 -070090 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 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 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700103
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 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),
Amith Yamasania90b7f02010-08-25 18:27:20 -0700108 (fl & ~Spanned.SPAN_POINT_MARK_MASK)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 | Spanned.SPAN_COMPOSING
110 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
111 }
112 }
113 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700114
115 text.setSpan(COMPOSING, start, end,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 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 /**
Gilles Debunne9d69ecb2012-02-24 16:07:09 -0800156 * 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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 * 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 /**
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800177 * Default implementation does nothing and returns false.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 */
179 public boolean commitCompletion(CompletionInfo text) {
180 return false;
181 }
182
183 /**
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800184 * Default implementation does nothing and returns false.
185 */
186 public boolean commitCorrection(CorrectionInfo correctionInfo) {
187 return false;
188 }
189
190 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 * 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.
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800205 * @param beforeLength
206 * @param afterLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 */
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800208 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
209 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
210 + " / " + afterLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 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
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800240 if (beforeLength > 0) {
241 int start = a - beforeLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 if (start < 0) start = 0;
243 content.delete(start, a);
244 deleted = a - start;
245 }
246
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800247 if (afterLength > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 b = b - deleted;
249
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800250 int end = b + afterLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 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
Dianne Hackborna465a172009-06-22 14:20:17 -0700325 if (a <= 0) {
326 return "";
327 }
328
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 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 /**
Amith Yamasania90b7f02010-08-25 18:27:20 -0700340 * 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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 * 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
Eric Fischer11278952010-03-08 16:38:03 -0800381 // Guard against the case where the cursor has not been positioned yet.
382 if (b < 0) {
383 b = 0;
384 }
385
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 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 /**
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700398 * The default implementation turns this into the enter key.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 */
400 public boolean performEditorAction(int actionCode) {
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700401 long eventTime = SystemClock.uptimeMillis();
402 sendKeyEvent(new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800403 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
404 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700405 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
406 | KeyEvent.FLAG_EDITOR_ACTION));
407 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800408 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
409 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700410 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
411 | KeyEvent.FLAG_EDITOR_ACTION));
412 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 }
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
Amith Yamasania90b7f02010-08-25 18:27:20 -0700440 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 }
Amith Yamasani41989182010-09-22 16:58:13 -0700452 // Clip the end points to be within the content bounds.
453 final int length = content.length();
Amith Yamasania90b7f02010-08-25 18:27:20 -0700454 if (a < 0) a = 0;
Amith Yamasani41989182010-09-22 16:58:13 -0700455 if (b < 0) b = 0;
456 if (a > length) a = length;
457 if (b > length) b = length;
Amith Yamasania90b7f02010-08-25 18:27:20 -0700458
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 /**
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 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700492 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 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) {
Jeff Browna175a5b2012-02-15 19:18:31 -0800509 ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
510 if (viewRootImpl == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 if (mIMM.mServedView != null) {
Jeff Browna175a5b2012-02-15 19:18:31 -0800512 viewRootImpl = mIMM.mServedView.getViewRootImpl();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 }
514 }
Jeff Browna175a5b2012-02-15 19:18:31 -0800515 if (viewRootImpl != null) {
516 viewRootImpl.dispatchKeyFromIme(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 }
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) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800545 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 }
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(),
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800563 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 sendKeyEvent(event);
565 content.clear();
566 }
567 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700568
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594 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);
Dianne Hackborna465a172009-06-22 14:20:17 -0700620 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 }
627 }
628
629 if (composing) {
630 Spannable sp = null;
631 if (!(text instanceof Spannable)) {
632 sp = new SpannableStringBuilder(text);
633 text = sp;
Amith Yamasania90b7f02010-08-25 18:27:20 -0700634 ensureDefaultComposingSpans();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 if (mDefaultComposingSpans != null) {
636 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
637 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
Amith Yamasania90b7f02010-08-25 18:27:20 -0700638 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 }
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 }
satoke3797a12011-03-22 06:34:48 +0900658
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800659 // 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}