blob: f730cf7c451463090488e9d2d447f4a97dc7713d [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);
Jean Chalardaaf86712013-03-01 15:08:14 -0800272 // Note: sendCurrentText does nothing unless mDummyMode is set
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 sendCurrentText();
Jean Chalardaaf86712013-03-01 15:08:14 -0800274 endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 }
276 return true;
277 }
278
279 /**
280 * The default implementation uses TextUtils.getCapsMode to get the
281 * cursor caps mode for the current selection position in the editable
282 * text, unless in dummy mode in which case 0 is always returned.
283 */
284 public int getCursorCapsMode(int reqModes) {
285 if (mDummyMode) return 0;
286
287 final Editable content = getEditable();
288 if (content == null) return 0;
289
290 int a = Selection.getSelectionStart(content);
291 int b = Selection.getSelectionEnd(content);
292
293 if (a > b) {
294 int tmp = a;
295 a = b;
296 b = tmp;
297 }
298
299 return TextUtils.getCapsMode(content, a, reqModes);
300 }
301
302 /**
303 * The default implementation always returns null.
304 */
305 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
306 return null;
307 }
308
309 /**
310 * The default implementation returns the given amount of text from the
311 * current cursor position in the buffer.
312 */
313 public CharSequence getTextBeforeCursor(int length, int flags) {
314 final Editable content = getEditable();
315 if (content == null) return null;
316
317 int a = Selection.getSelectionStart(content);
318 int b = Selection.getSelectionEnd(content);
319
320 if (a > b) {
321 int tmp = a;
322 a = b;
323 b = tmp;
324 }
325
Dianne Hackborna465a172009-06-22 14:20:17 -0700326 if (a <= 0) {
327 return "";
328 }
329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 if (length > a) {
331 length = a;
332 }
333
334 if ((flags&GET_TEXT_WITH_STYLES) != 0) {
335 return content.subSequence(a - length, a);
336 }
337 return TextUtils.substring(content, a - length, a);
338 }
339
340 /**
Amith Yamasania90b7f02010-08-25 18:27:20 -0700341 * The default implementation returns the text currently selected, or null if none is
342 * selected.
343 */
344 public CharSequence getSelectedText(int flags) {
345 final Editable content = getEditable();
346 if (content == null) return null;
347
348 int a = Selection.getSelectionStart(content);
349 int b = Selection.getSelectionEnd(content);
350
351 if (a > b) {
352 int tmp = a;
353 a = b;
354 b = tmp;
355 }
356
357 if (a == b) return null;
358
359 if ((flags&GET_TEXT_WITH_STYLES) != 0) {
360 return content.subSequence(a, b);
361 }
362 return TextUtils.substring(content, a, b);
363 }
364
365 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 * The default implementation returns the given amount of text from the
367 * current cursor position in the buffer.
368 */
369 public CharSequence getTextAfterCursor(int length, int flags) {
370 final Editable content = getEditable();
371 if (content == null) return null;
372
373 int a = Selection.getSelectionStart(content);
374 int b = Selection.getSelectionEnd(content);
375
376 if (a > b) {
377 int tmp = a;
378 a = b;
379 b = tmp;
380 }
381
Eric Fischer11278952010-03-08 16:38:03 -0800382 // Guard against the case where the cursor has not been positioned yet.
383 if (b < 0) {
384 b = 0;
385 }
386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 if (b + length > content.length()) {
388 length = content.length() - b;
389 }
390
391
392 if ((flags&GET_TEXT_WITH_STYLES) != 0) {
393 return content.subSequence(b, b + length);
394 }
395 return TextUtils.substring(content, b, b + length);
396 }
397
398 /**
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700399 * The default implementation turns this into the enter key.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 */
401 public boolean performEditorAction(int actionCode) {
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700402 long eventTime = SystemClock.uptimeMillis();
403 sendKeyEvent(new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800404 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
405 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700406 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
407 | KeyEvent.FLAG_EDITOR_ACTION));
408 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800409 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
410 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700411 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
412 | KeyEvent.FLAG_EDITOR_ACTION));
413 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 }
415
416 /**
417 * The default implementation does nothing.
418 */
419 public boolean performContextMenuAction(int id) {
420 return false;
421 }
422
423 /**
424 * The default implementation does nothing.
425 */
426 public boolean performPrivateCommand(String action, Bundle data) {
427 return false;
428 }
429
430 /**
431 * The default implementation places the given text into the editable,
432 * replacing any existing composing text. The new text is marked as
433 * in a composing state with the composing style.
434 */
435 public boolean setComposingText(CharSequence text, int newCursorPosition) {
436 if (DEBUG) Log.v(TAG, "setComposingText " + text);
437 replaceText(text, newCursorPosition, true);
438 return true;
439 }
440
Amith Yamasania90b7f02010-08-25 18:27:20 -0700441 public boolean setComposingRegion(int start, int end) {
442 final Editable content = getEditable();
443 if (content != null) {
444 beginBatchEdit();
445 removeComposingSpans(content);
446 int a = start;
447 int b = end;
448 if (a > b) {
449 int tmp = a;
450 a = b;
451 b = tmp;
452 }
Amith Yamasani41989182010-09-22 16:58:13 -0700453 // Clip the end points to be within the content bounds.
454 final int length = content.length();
Amith Yamasania90b7f02010-08-25 18:27:20 -0700455 if (a < 0) a = 0;
Amith Yamasani41989182010-09-22 16:58:13 -0700456 if (b < 0) b = 0;
457 if (a > length) a = length;
458 if (b > length) b = length;
Amith Yamasania90b7f02010-08-25 18:27:20 -0700459
460 ensureDefaultComposingSpans();
461 if (mDefaultComposingSpans != null) {
462 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
463 content.setSpan(mDefaultComposingSpans[i], a, b,
464 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
465 }
466 }
467
468 content.setSpan(COMPOSING, a, b,
469 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
470
Jean Chalardaaf86712013-03-01 15:08:14 -0800471 // Note: sendCurrentText does nothing unless mDummyMode is set
Amith Yamasania90b7f02010-08-25 18:27:20 -0700472 sendCurrentText();
Jean Chalardaaf86712013-03-01 15:08:14 -0800473 endBatchEdit();
Amith Yamasania90b7f02010-08-25 18:27:20 -0700474 }
475 return true;
476 }
477
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 /**
479 * The default implementation changes the selection position in the
480 * current editable text.
481 */
482 public boolean setSelection(int start, int end) {
483 if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
484 final Editable content = getEditable();
485 if (content == null) return false;
486 int len = content.length();
487 if (start > len || end > len) {
488 // If the given selection is out of bounds, just ignore it.
489 // Most likely the text was changed out from under the IME,
490 // the the IME is going to have to update all of its state
491 // anyway.
492 return true;
493 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700494 if (start == end && MetaKeyKeyListener.getMetaState(content,
495 MetaKeyKeyListener.META_SELECTING) != 0) {
496 // If we are in selection mode, then we want to extend the
497 // selection instead of replacing it.
498 Selection.extendSelection(content, start);
499 } else {
500 Selection.setSelection(content, start, end);
501 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 return true;
503 }
504
505 /**
506 * Provides standard implementation for sending a key event to the window
507 * attached to the input connection's view.
508 */
509 public boolean sendKeyEvent(KeyEvent event) {
510 synchronized (mIMM.mH) {
Jeff Browna175a5b2012-02-15 19:18:31 -0800511 ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
512 if (viewRootImpl == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 if (mIMM.mServedView != null) {
Jeff Browna175a5b2012-02-15 19:18:31 -0800514 viewRootImpl = mIMM.mServedView.getViewRootImpl();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 }
516 }
Jeff Browna175a5b2012-02-15 19:18:31 -0800517 if (viewRootImpl != null) {
518 viewRootImpl.dispatchKeyFromIme(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 }
520 }
521 return false;
522 }
523
524 /**
525 * Updates InputMethodManager with the current fullscreen mode.
526 */
527 public boolean reportFullscreenMode(boolean enabled) {
528 mIMM.setFullscreenMode(enabled);
529 return true;
530 }
531
532 private void sendCurrentText() {
533 if (!mDummyMode) {
534 return;
535 }
536
537 Editable content = getEditable();
538 if (content != null) {
539 final int N = content.length();
540 if (N == 0) {
541 return;
542 }
543 if (N == 1) {
544 // If it's 1 character, we have a chance of being
545 // able to generate normal key events...
546 if (mKeyCharacterMap == null) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800547 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 }
549 char[] chars = new char[1];
550 content.getChars(0, 1, chars, 0);
551 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
552 if (events != null) {
553 for (int i=0; i<events.length; i++) {
554 if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
555 sendKeyEvent(events[i]);
556 }
557 content.clear();
558 return;
559 }
560 }
561
562 // Otherwise, revert to the special key event containing
563 // the actual characters.
564 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800565 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 sendKeyEvent(event);
567 content.clear();
568 }
569 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700570
571 private void ensureDefaultComposingSpans() {
572 if (mDefaultComposingSpans == null) {
573 Context context;
574 if (mTargetView != null) {
575 context = mTargetView.getContext();
576 } else if (mIMM.mServedView != null) {
577 context = mIMM.mServedView.getContext();
578 } else {
579 context = null;
580 }
581 if (context != null) {
582 TypedArray ta = context.getTheme()
583 .obtainStyledAttributes(new int[] {
584 com.android.internal.R.attr.candidatesTextStyleSpans
585 });
586 CharSequence style = ta.getText(0);
587 ta.recycle();
588 if (style != null && style instanceof Spanned) {
589 mDefaultComposingSpans = ((Spanned)style).getSpans(
590 0, style.length(), Object.class);
591 }
592 }
593 }
594 }
595
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 private void replaceText(CharSequence text, int newCursorPosition,
597 boolean composing) {
598 final Editable content = getEditable();
599 if (content == null) {
600 return;
601 }
602
603 beginBatchEdit();
604
605 // delete composing text set previously.
606 int a = getComposingSpanStart(content);
607 int b = getComposingSpanEnd(content);
608
609 if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
610
611 if (b < a) {
612 int tmp = a;
613 a = b;
614 b = tmp;
615 }
616
617 if (a != -1 && b != -1) {
618 removeComposingSpans(content);
619 } else {
620 a = Selection.getSelectionStart(content);
621 b = Selection.getSelectionEnd(content);
Dianne Hackborna465a172009-06-22 14:20:17 -0700622 if (a < 0) a = 0;
623 if (b < 0) b = 0;
624 if (b < a) {
625 int tmp = a;
626 a = b;
627 b = tmp;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 }
629 }
630
631 if (composing) {
632 Spannable sp = null;
633 if (!(text instanceof Spannable)) {
634 sp = new SpannableStringBuilder(text);
635 text = sp;
Amith Yamasania90b7f02010-08-25 18:27:20 -0700636 ensureDefaultComposingSpans();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 if (mDefaultComposingSpans != null) {
638 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
639 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
Amith Yamasania90b7f02010-08-25 18:27:20 -0700640 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 }
642 }
643 } else {
644 sp = (Spannable)text;
645 }
646 setComposingSpans(sp);
647 }
648
649 if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
650 + text + "\", composing=" + composing
651 + ", type=" + text.getClass().getCanonicalName());
652
653 if (DEBUG) {
654 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
655 lp.println("Current text:");
656 TextUtils.dumpSpans(content, lp, " ");
657 lp.println("Composing text:");
658 TextUtils.dumpSpans(text, lp, " ");
659 }
satoke3797a12011-03-22 06:34:48 +0900660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 // Position the cursor appropriately, so that after replacing the
662 // desired range of text it will be located in the correct spot.
663 // This allows us to deal with filters performing edits on the text
664 // we are providing here.
665 if (newCursorPosition > 0) {
666 newCursorPosition += b - 1;
667 } else {
668 newCursorPosition += a;
669 }
670 if (newCursorPosition < 0) newCursorPosition = 0;
671 if (newCursorPosition > content.length())
672 newCursorPosition = content.length();
673 Selection.setSelection(content, newCursorPosition);
674
675 content.replace(a, b, text);
676
677 if (DEBUG) {
678 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
679 lp.println("Final text:");
680 TextUtils.dumpSpans(content, lp, " ");
681 }
682
683 endBatchEdit();
684 }
685}