blob: a3c49c5a84570bbbd48f83b14b4cd3f67d901f5d [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;
Yohei Yukawa612cce92016-02-11 17:47:33 -080022import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import 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;
Dianne Hackborn6dd005b2011-07-18 13:22:50 -070037import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
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
Jean Chalarde811de22013-05-24 08:06:28 +090046 * {@link #getEditable} to provide access to their own editable object, and
47 * to refer to the documentation in {@link InputConnection}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 */
49public class BaseInputConnection implements InputConnection {
50 private static final boolean DEBUG = false;
51 private static final String TAG = "BaseInputConnection";
52 static final Object COMPOSING = new ComposingText();
satokf9f01002011-05-19 21:31:50 +090053
54 /** @hide */
55 protected final InputMethodManager mIMM;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 final View mTargetView;
57 final boolean mDummyMode;
58
59 private Object[] mDefaultComposingSpans;
60
61 Editable mEditable;
62 KeyCharacterMap mKeyCharacterMap;
63
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070064 BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 mIMM = mgr;
66 mTargetView = null;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070067 mDummyMode = !fullEditor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 }
69
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070070 public BaseInputConnection(View targetView, boolean fullEditor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 mIMM = (InputMethodManager)targetView.getContext().getSystemService(
72 Context.INPUT_METHOD_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 mTargetView = targetView;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070074 mDummyMode = !fullEditor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 }
76
77 public static final void removeComposingSpans(Spannable text) {
78 text.removeSpan(COMPOSING);
79 Object[] sps = text.getSpans(0, text.length(), Object.class);
80 if (sps != null) {
81 for (int i=sps.length-1; i>=0; i--) {
82 Object o = sps[i];
83 if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
84 text.removeSpan(o);
85 }
86 }
87 }
88 }
Amith Yamasania90b7f02010-08-25 18:27:20 -070089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 public static void setComposingSpans(Spannable text) {
Amith Yamasania90b7f02010-08-25 18:27:20 -070091 setComposingSpans(text, 0, text.length());
92 }
93
94 /** @hide */
95 public static void setComposingSpans(Spannable text, int start, int end) {
96 final Object[] sps = text.getSpans(start, end, Object.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 if (sps != null) {
98 for (int i=sps.length-1; i>=0; i--) {
99 final Object o = sps[i];
100 if (o == COMPOSING) {
101 text.removeSpan(o);
102 continue;
103 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700104
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 final int fl = text.getSpanFlags(o);
106 if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
107 != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
108 text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
Amith Yamasania90b7f02010-08-25 18:27:20 -0700109 (fl & ~Spanned.SPAN_POINT_MARK_MASK)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 | Spanned.SPAN_COMPOSING
111 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
112 }
113 }
114 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700115
116 text.setSpan(COMPOSING, start, end,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
118 }
119
120 public static int getComposingSpanStart(Spannable text) {
121 return text.getSpanStart(COMPOSING);
122 }
123
124 public static int getComposingSpanEnd(Spannable text) {
125 return text.getSpanEnd(COMPOSING);
126 }
127
128 /**
129 * Return the target of edit operations. The default implementation
130 * returns its own fake editable that is just used for composing text;
131 * subclasses that are real text editors should override this and
132 * supply their own.
133 */
134 public Editable getEditable() {
135 if (mEditable == null) {
136 mEditable = Editable.Factory.getInstance().newEditable("");
137 Selection.setSelection(mEditable, 0);
138 }
139 return mEditable;
140 }
141
142 /**
143 * Default implementation does nothing.
144 */
145 public boolean beginBatchEdit() {
146 return false;
147 }
148
149 /**
150 * Default implementation does nothing.
151 */
152 public boolean endBatchEdit() {
153 return false;
154 }
155
156 /**
Gilles Debunne9d69ecb2012-02-24 16:07:09 -0800157 * Called when this InputConnection is no longer used by the InputMethodManager.
158 *
159 * @hide
160 */
Yohei Yukawaaaa38c92016-03-27 23:46:04 -0700161 public void reportFinish() {
162 // Intentionally empty
Gilles Debunne9d69ecb2012-02-24 16:07:09 -0800163 }
164
165 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 * Default implementation uses
167 * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
168 * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
169 */
170 public boolean clearMetaKeyStates(int states) {
171 final Editable content = getEditable();
172 if (content == null) return false;
173 MetaKeyKeyListener.clearMetaKeyState(content, states);
174 return true;
175 }
176
177 /**
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800178 * Default implementation does nothing and returns false.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 */
180 public boolean commitCompletion(CompletionInfo text) {
181 return false;
182 }
183
184 /**
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800185 * Default implementation does nothing and returns false.
186 */
187 public boolean commitCorrection(CorrectionInfo correctionInfo) {
188 return false;
189 }
190
191 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 * Default implementation replaces any existing composing text with
193 * the given text. In addition, only if dummy mode, a key event is
194 * sent for the new text and the current editable buffer cleared.
195 */
196 public boolean commitText(CharSequence text, int newCursorPosition) {
197 if (DEBUG) Log.v(TAG, "commitText " + text);
198 replaceText(text, newCursorPosition, false);
199 sendCurrentText();
200 return true;
201 }
202
203 /**
Yohei Yukawa5f137932016-01-06 15:57:27 -0800204 * The default implementation performs the deletion around the current selection position of the
205 * editable text.
206 *
207 * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
208 * If this is greater than the number of existing characters between the beginning of the
209 * text and the cursor, then this method does not fail but deletes all the characters in
210 * that range.
211 * @param afterLength The number of characters after the cursor to be deleted, in code unit.
212 * If this is greater than the number of existing characters between the cursor and
213 * the end of the text, then this method does not fail but deletes all the characters in
214 * that range.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 */
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800216 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
217 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
218 + " / " + afterLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 final Editable content = getEditable();
220 if (content == null) return false;
221
222 beginBatchEdit();
Yohei Yukawa5f137932016-01-06 15:57:27 -0800223
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 int a = Selection.getSelectionStart(content);
225 int b = Selection.getSelectionEnd(content);
226
227 if (a > b) {
228 int tmp = a;
229 a = b;
230 b = tmp;
231 }
232
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800233 // Ignore the composing text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 int ca = getComposingSpanStart(content);
235 int cb = getComposingSpanEnd(content);
236 if (cb < ca) {
237 int tmp = ca;
238 ca = cb;
239 cb = tmp;
240 }
241 if (ca != -1 && cb != -1) {
242 if (ca < a) a = ca;
243 if (cb > b) b = cb;
244 }
245
246 int deleted = 0;
247
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800248 if (beforeLength > 0) {
249 int start = a - beforeLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 if (start < 0) start = 0;
251 content.delete(start, a);
252 deleted = a - start;
253 }
254
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800255 if (afterLength > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 b = b - deleted;
257
Fabrice Di Meglio0c95dd32012-01-23 15:06:42 -0800258 int end = b + afterLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 if (end > content.length()) end = content.length();
260
261 content.delete(b, end);
262 }
Yohei Yukawa5f137932016-01-06 15:57:27 -0800263
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 endBatchEdit();
Yohei Yukawa5f137932016-01-06 15:57:27 -0800265
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 return true;
267 }
268
Yohei Yukawac89e22a2016-01-13 22:48:14 -0800269 private static int INVALID_INDEX = -1;
270 private static int findIndexBackward(final CharSequence cs, final int from,
271 final int numCodePoints) {
272 int currentIndex = from;
273 boolean waitingHighSurrogate = false;
274 final int N = cs.length();
275 if (currentIndex < 0 || N < currentIndex) {
276 return INVALID_INDEX; // The starting point is out of range.
277 }
278 if (numCodePoints < 0) {
279 return INVALID_INDEX; // Basically this should not happen.
280 }
281 int remainingCodePoints = numCodePoints;
282 while (true) {
283 if (remainingCodePoints == 0) {
284 return currentIndex; // Reached to the requested length in code points.
285 }
286
287 --currentIndex;
288 if (currentIndex < 0) {
289 if (waitingHighSurrogate) {
290 return INVALID_INDEX; // An invalid surrogate pair is found.
291 }
292 return 0; // Reached to the beginning of the text w/o any invalid surrogate pair.
293 }
294 final char c = cs.charAt(currentIndex);
295 if (waitingHighSurrogate) {
296 if (!java.lang.Character.isHighSurrogate(c)) {
297 return INVALID_INDEX; // An invalid surrogate pair is found.
298 }
299 waitingHighSurrogate = false;
300 --remainingCodePoints;
301 continue;
302 }
303 if (!java.lang.Character.isSurrogate(c)) {
304 --remainingCodePoints;
305 continue;
306 }
307 if (java.lang.Character.isHighSurrogate(c)) {
308 return INVALID_INDEX; // A invalid surrogate pair is found.
309 }
310 waitingHighSurrogate = true;
311 }
312 }
313
314 private static int findIndexForward(final CharSequence cs, final int from,
315 final int numCodePoints) {
316 int currentIndex = from;
317 boolean waitingLowSurrogate = false;
318 final int N = cs.length();
319 if (currentIndex < 0 || N < currentIndex) {
320 return INVALID_INDEX; // The starting point is out of range.
321 }
322 if (numCodePoints < 0) {
323 return INVALID_INDEX; // Basically this should not happen.
324 }
325 int remainingCodePoints = numCodePoints;
326
327 while (true) {
328 if (remainingCodePoints == 0) {
329 return currentIndex; // Reached to the requested length in code points.
330 }
331
332 if (currentIndex >= N) {
333 if (waitingLowSurrogate) {
334 return INVALID_INDEX; // An invalid surrogate pair is found.
335 }
336 return N; // Reached to the end of the text w/o any invalid surrogate pair.
337 }
338 final char c = cs.charAt(currentIndex);
339 if (waitingLowSurrogate) {
340 if (!java.lang.Character.isLowSurrogate(c)) {
341 return INVALID_INDEX; // An invalid surrogate pair is found.
342 }
343 --remainingCodePoints;
344 waitingLowSurrogate = false;
345 ++currentIndex;
346 continue;
347 }
348 if (!java.lang.Character.isSurrogate(c)) {
349 --remainingCodePoints;
350 ++currentIndex;
351 continue;
352 }
353 if (java.lang.Character.isLowSurrogate(c)) {
354 return INVALID_INDEX; // A invalid surrogate pair is found.
355 }
356 waitingLowSurrogate = true;
357 ++currentIndex;
358 }
359 }
360
361 /**
362 * The default implementation performs the deletion around the current selection position of the
363 * editable text.
364 * @param beforeLength The number of characters before the cursor to be deleted, in code points.
365 * If this is greater than the number of existing characters between the beginning of the
366 * text and the cursor, then this method does not fail but deletes all the characters in
367 * that range.
368 * @param afterLength The number of characters after the cursor to be deleted, in code points.
369 * If this is greater than the number of existing characters between the cursor and
370 * the end of the text, then this method does not fail but deletes all the characters in
371 * that range.
372 */
373 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
374 if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
375 + " / " + afterLength);
376 final Editable content = getEditable();
377 if (content == null) return false;
378
379 beginBatchEdit();
380
381 int a = Selection.getSelectionStart(content);
382 int b = Selection.getSelectionEnd(content);
383
384 if (a > b) {
385 int tmp = a;
386 a = b;
387 b = tmp;
388 }
389
390 // Ignore the composing text.
391 int ca = getComposingSpanStart(content);
392 int cb = getComposingSpanEnd(content);
393 if (cb < ca) {
394 int tmp = ca;
395 ca = cb;
396 cb = tmp;
397 }
398 if (ca != -1 && cb != -1) {
399 if (ca < a) a = ca;
400 if (cb > b) b = cb;
401 }
402
403 if (a >= 0 && b >= 0) {
404 final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
405 if (start != INVALID_INDEX) {
406 final int end = findIndexForward(content, b, Math.max(afterLength, 0));
407 if (end != INVALID_INDEX) {
408 final int numDeleteBefore = a - start;
409 if (numDeleteBefore > 0) {
410 content.delete(start, a);
411 }
412 final int numDeleteAfter = end - b;
413 if (numDeleteAfter > 0) {
414 content.delete(b - numDeleteBefore, end - numDeleteBefore);
415 }
416 }
417 }
418 // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
419 // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
420 // always returns true to the IME without waiting for the completion of this method as
421 // IInputConnectionWrapper#isAtive() returns true. This is actually why some methods
422 // including this method look like asynchronous calls from the IME.
423 }
424
425 endBatchEdit();
426
427 return true;
428 }
429
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 /**
431 * The default implementation removes the composing state from the
432 * current editable text. In addition, only if dummy mode, a key event is
433 * sent for the new text and the current editable buffer cleared.
434 */
435 public boolean finishComposingText() {
436 if (DEBUG) Log.v(TAG, "finishComposingText");
437 final Editable content = getEditable();
438 if (content != null) {
439 beginBatchEdit();
440 removeComposingSpans(content);
Jean Chalardaaf86712013-03-01 15:08:14 -0800441 // Note: sendCurrentText does nothing unless mDummyMode is set
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 sendCurrentText();
Jean Chalardaaf86712013-03-01 15:08:14 -0800443 endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 }
445 return true;
446 }
447
448 /**
449 * The default implementation uses TextUtils.getCapsMode to get the
450 * cursor caps mode for the current selection position in the editable
451 * text, unless in dummy mode in which case 0 is always returned.
452 */
453 public int getCursorCapsMode(int reqModes) {
454 if (mDummyMode) return 0;
455
456 final Editable content = getEditable();
457 if (content == null) return 0;
458
459 int a = Selection.getSelectionStart(content);
460 int b = Selection.getSelectionEnd(content);
461
462 if (a > b) {
463 int tmp = a;
464 a = b;
465 b = tmp;
466 }
467
468 return TextUtils.getCapsMode(content, a, reqModes);
469 }
470
471 /**
472 * The default implementation always returns null.
473 */
474 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
475 return null;
476 }
477
478 /**
479 * The default implementation returns the given amount of text from the
480 * current cursor position in the buffer.
481 */
482 public CharSequence getTextBeforeCursor(int length, int flags) {
483 final Editable content = getEditable();
484 if (content == null) return null;
485
486 int a = Selection.getSelectionStart(content);
487 int b = Selection.getSelectionEnd(content);
488
489 if (a > b) {
490 int tmp = a;
491 a = b;
492 b = tmp;
493 }
494
Dianne Hackborna465a172009-06-22 14:20:17 -0700495 if (a <= 0) {
496 return "";
497 }
498
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 if (length > a) {
500 length = a;
501 }
502
503 if ((flags&GET_TEXT_WITH_STYLES) != 0) {
504 return content.subSequence(a - length, a);
505 }
506 return TextUtils.substring(content, a - length, a);
507 }
508
509 /**
Amith Yamasania90b7f02010-08-25 18:27:20 -0700510 * The default implementation returns the text currently selected, or null if none is
511 * selected.
512 */
513 public CharSequence getSelectedText(int flags) {
514 final Editable content = getEditable();
515 if (content == null) return null;
516
517 int a = Selection.getSelectionStart(content);
518 int b = Selection.getSelectionEnd(content);
519
520 if (a > b) {
521 int tmp = a;
522 a = b;
523 b = tmp;
524 }
525
526 if (a == b) return null;
527
528 if ((flags&GET_TEXT_WITH_STYLES) != 0) {
529 return content.subSequence(a, b);
530 }
531 return TextUtils.substring(content, a, b);
532 }
533
534 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 * The default implementation returns the given amount of text from the
536 * current cursor position in the buffer.
537 */
538 public CharSequence getTextAfterCursor(int length, int flags) {
539 final Editable content = getEditable();
540 if (content == null) return null;
541
542 int a = Selection.getSelectionStart(content);
543 int b = Selection.getSelectionEnd(content);
544
545 if (a > b) {
546 int tmp = a;
547 a = b;
548 b = tmp;
549 }
550
Eric Fischer11278952010-03-08 16:38:03 -0800551 // Guard against the case where the cursor has not been positioned yet.
552 if (b < 0) {
553 b = 0;
554 }
555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 if (b + length > content.length()) {
557 length = content.length() - b;
558 }
559
560
561 if ((flags&GET_TEXT_WITH_STYLES) != 0) {
562 return content.subSequence(b, b + length);
563 }
564 return TextUtils.substring(content, b, b + length);
565 }
566
567 /**
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700568 * The default implementation turns this into the enter key.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 */
570 public boolean performEditorAction(int actionCode) {
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700571 long eventTime = SystemClock.uptimeMillis();
572 sendKeyEvent(new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800573 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
574 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700575 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
576 | KeyEvent.FLAG_EDITOR_ACTION));
577 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800578 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
579 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
Dianne Hackborn86d56cc2009-06-29 12:00:39 -0700580 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
581 | KeyEvent.FLAG_EDITOR_ACTION));
582 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 }
584
585 /**
586 * The default implementation does nothing.
587 */
588 public boolean performContextMenuAction(int id) {
589 return false;
590 }
591
592 /**
593 * The default implementation does nothing.
594 */
595 public boolean performPrivateCommand(String action, Bundle data) {
596 return false;
597 }
598
599 /**
Yohei Yukawaa277db22014-08-21 18:38:44 -0700600 * The default implementation does nothing.
Yohei Yukawa0023d0e2014-07-11 04:13:03 +0900601 */
Yohei Yukawad8636ea2014-09-02 22:03:30 -0700602 public boolean requestCursorUpdates(int cursorUpdateMode) {
603 return false;
604 }
605
Yohei Yukawa612cce92016-02-11 17:47:33 -0800606 public Handler getHandler() {
607 return null;
608 }
609
Yohei Yukawad8636ea2014-09-02 22:03:30 -0700610 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 * The default implementation places the given text into the editable,
612 * replacing any existing composing text. The new text is marked as
613 * in a composing state with the composing style.
614 */
615 public boolean setComposingText(CharSequence text, int newCursorPosition) {
616 if (DEBUG) Log.v(TAG, "setComposingText " + text);
617 replaceText(text, newCursorPosition, true);
618 return true;
619 }
620
Amith Yamasania90b7f02010-08-25 18:27:20 -0700621 public boolean setComposingRegion(int start, int end) {
622 final Editable content = getEditable();
623 if (content != null) {
624 beginBatchEdit();
625 removeComposingSpans(content);
626 int a = start;
627 int b = end;
628 if (a > b) {
629 int tmp = a;
630 a = b;
631 b = tmp;
632 }
Amith Yamasani41989182010-09-22 16:58:13 -0700633 // Clip the end points to be within the content bounds.
634 final int length = content.length();
Amith Yamasania90b7f02010-08-25 18:27:20 -0700635 if (a < 0) a = 0;
Amith Yamasani41989182010-09-22 16:58:13 -0700636 if (b < 0) b = 0;
637 if (a > length) a = length;
638 if (b > length) b = length;
Amith Yamasania90b7f02010-08-25 18:27:20 -0700639
640 ensureDefaultComposingSpans();
641 if (mDefaultComposingSpans != null) {
642 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
643 content.setSpan(mDefaultComposingSpans[i], a, b,
644 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
645 }
646 }
647
648 content.setSpan(COMPOSING, a, b,
649 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
650
Jean Chalardaaf86712013-03-01 15:08:14 -0800651 // Note: sendCurrentText does nothing unless mDummyMode is set
Amith Yamasania90b7f02010-08-25 18:27:20 -0700652 sendCurrentText();
Jean Chalardaaf86712013-03-01 15:08:14 -0800653 endBatchEdit();
Amith Yamasania90b7f02010-08-25 18:27:20 -0700654 }
655 return true;
656 }
657
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 /**
659 * The default implementation changes the selection position in the
660 * current editable text.
661 */
662 public boolean setSelection(int start, int end) {
663 if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
664 final Editable content = getEditable();
665 if (content == null) return false;
666 int len = content.length();
Yohei Yukawaef090412014-01-17 08:59:07 +0900667 if (start > len || end > len || start < 0 || end < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 // If the given selection is out of bounds, just ignore it.
669 // Most likely the text was changed out from under the IME,
Yohei Yukawaef090412014-01-17 08:59:07 +0900670 // and the IME is going to have to update all of its state
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671 // anyway.
672 return true;
673 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700674 if (start == end && MetaKeyKeyListener.getMetaState(content,
675 MetaKeyKeyListener.META_SELECTING) != 0) {
676 // If we are in selection mode, then we want to extend the
677 // selection instead of replacing it.
678 Selection.extendSelection(content, start);
679 } else {
680 Selection.setSelection(content, start, end);
681 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 return true;
683 }
684
685 /**
686 * Provides standard implementation for sending a key event to the window
687 * attached to the input connection's view.
688 */
689 public boolean sendKeyEvent(KeyEvent event) {
Yohei Yukawa2afe2aa2016-01-07 18:09:44 -0800690 mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691 return false;
692 }
Yohei Yukawa159dd472016-01-07 16:52:33 -0800693
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 /**
695 * Updates InputMethodManager with the current fullscreen mode.
696 */
697 public boolean reportFullscreenMode(boolean enabled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 return true;
699 }
Yohei Yukawa159dd472016-01-07 16:52:33 -0800700
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 private void sendCurrentText() {
702 if (!mDummyMode) {
703 return;
704 }
705
706 Editable content = getEditable();
707 if (content != null) {
708 final int N = content.length();
709 if (N == 0) {
710 return;
711 }
712 if (N == 1) {
713 // If it's 1 character, we have a chance of being
714 // able to generate normal key events...
715 if (mKeyCharacterMap == null) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800716 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800717 }
718 char[] chars = new char[1];
719 content.getChars(0, 1, chars, 0);
720 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
721 if (events != null) {
722 for (int i=0; i<events.length; i++) {
723 if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
724 sendKeyEvent(events[i]);
725 }
726 content.clear();
727 return;
728 }
729 }
730
731 // Otherwise, revert to the special key event containing
732 // the actual characters.
733 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800734 content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 sendKeyEvent(event);
736 content.clear();
737 }
738 }
Amith Yamasania90b7f02010-08-25 18:27:20 -0700739
740 private void ensureDefaultComposingSpans() {
741 if (mDefaultComposingSpans == null) {
742 Context context;
743 if (mTargetView != null) {
744 context = mTargetView.getContext();
745 } else if (mIMM.mServedView != null) {
746 context = mIMM.mServedView.getContext();
747 } else {
748 context = null;
749 }
750 if (context != null) {
751 TypedArray ta = context.getTheme()
752 .obtainStyledAttributes(new int[] {
753 com.android.internal.R.attr.candidatesTextStyleSpans
754 });
755 CharSequence style = ta.getText(0);
756 ta.recycle();
757 if (style != null && style instanceof Spanned) {
758 mDefaultComposingSpans = ((Spanned)style).getSpans(
759 0, style.length(), Object.class);
760 }
761 }
762 }
763 }
764
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 private void replaceText(CharSequence text, int newCursorPosition,
766 boolean composing) {
767 final Editable content = getEditable();
768 if (content == null) {
769 return;
770 }
771
772 beginBatchEdit();
Satoshi Kataokad7443c82013-10-15 17:45:43 +0900773
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 // delete composing text set previously.
775 int a = getComposingSpanStart(content);
776 int b = getComposingSpanEnd(content);
777
778 if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
779
780 if (b < a) {
781 int tmp = a;
782 a = b;
783 b = tmp;
784 }
785
786 if (a != -1 && b != -1) {
787 removeComposingSpans(content);
788 } else {
789 a = Selection.getSelectionStart(content);
790 b = Selection.getSelectionEnd(content);
Dianne Hackborna465a172009-06-22 14:20:17 -0700791 if (a < 0) a = 0;
792 if (b < 0) b = 0;
793 if (b < a) {
794 int tmp = a;
795 a = b;
796 b = tmp;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
798 }
799
800 if (composing) {
801 Spannable sp = null;
802 if (!(text instanceof Spannable)) {
803 sp = new SpannableStringBuilder(text);
804 text = sp;
Amith Yamasania90b7f02010-08-25 18:27:20 -0700805 ensureDefaultComposingSpans();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 if (mDefaultComposingSpans != null) {
807 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
808 sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
Amith Yamasania90b7f02010-08-25 18:27:20 -0700809 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 }
811 }
812 } else {
813 sp = (Spannable)text;
814 }
815 setComposingSpans(sp);
816 }
817
818 if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
819 + text + "\", composing=" + composing
820 + ", type=" + text.getClass().getCanonicalName());
821
822 if (DEBUG) {
823 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
824 lp.println("Current text:");
825 TextUtils.dumpSpans(content, lp, " ");
826 lp.println("Composing text:");
827 TextUtils.dumpSpans(text, lp, " ");
828 }
satoke3797a12011-03-22 06:34:48 +0900829
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 // Position the cursor appropriately, so that after replacing the
831 // desired range of text it will be located in the correct spot.
832 // This allows us to deal with filters performing edits on the text
833 // we are providing here.
834 if (newCursorPosition > 0) {
835 newCursorPosition += b - 1;
836 } else {
837 newCursorPosition += a;
838 }
839 if (newCursorPosition < 0) newCursorPosition = 0;
840 if (newCursorPosition > content.length())
841 newCursorPosition = content.length();
842 Selection.setSelection(content, newCursorPosition);
843
844 content.replace(a, b, text);
845
846 if (DEBUG) {
847 LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
848 lp.println("Final text:");
849 TextUtils.dumpSpans(content, lp, " ");
850 }
851
852 endBatchEdit();
853 }
854}