• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.view.inputmethod;
18 
19 import android.annotation.CallSuper;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.SystemClock;
25 import android.text.Editable;
26 import android.text.NoCopySpan;
27 import android.text.Selection;
28 import android.text.Spannable;
29 import android.text.SpannableStringBuilder;
30 import android.text.Spanned;
31 import android.text.TextUtils;
32 import android.text.method.MetaKeyKeyListener;
33 import android.util.Log;
34 import android.util.LogPrinter;
35 import android.view.KeyCharacterMap;
36 import android.view.KeyEvent;
37 import android.view.View;
38 
39 class 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, and
47  * to refer to the documentation in {@link InputConnection}.
48  */
49 public 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();
53 
54     /** @hide */
55     protected final InputMethodManager mIMM;
56     final View mTargetView;
57     final boolean mDummyMode;
58 
59     private Object[] mDefaultComposingSpans;
60 
61     Editable mEditable;
62     KeyCharacterMap mKeyCharacterMap;
63 
BaseInputConnection(InputMethodManager mgr, boolean fullEditor)64     BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
65         mIMM = mgr;
66         mTargetView = null;
67         mDummyMode = !fullEditor;
68     }
69 
BaseInputConnection(View targetView, boolean fullEditor)70     public BaseInputConnection(View targetView, boolean fullEditor) {
71         mIMM = (InputMethodManager)targetView.getContext().getSystemService(
72                 Context.INPUT_METHOD_SERVICE);
73         mTargetView = targetView;
74         mDummyMode = !fullEditor;
75     }
76 
removeComposingSpans(Spannable text)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     }
89 
setComposingSpans(Spannable text)90     public static void setComposingSpans(Spannable text) {
91         setComposingSpans(text, 0, text.length());
92     }
93 
94     /** @hide */
setComposingSpans(Spannable text, int start, int end)95     public static void setComposingSpans(Spannable text, int start, int end) {
96         final Object[] sps = text.getSpans(start, end, Object.class);
97         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                 }
104 
105                 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),
109                             (fl & ~Spanned.SPAN_POINT_MARK_MASK)
110                                     | Spanned.SPAN_COMPOSING
111                                     | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
112                 }
113             }
114         }
115 
116         text.setSpan(COMPOSING, start, end,
117                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
118     }
119 
getComposingSpanStart(Spannable text)120     public static int getComposingSpanStart(Spannable text) {
121         return text.getSpanStart(COMPOSING);
122     }
123 
getComposingSpanEnd(Spannable text)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      */
getEditable()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      */
beginBatchEdit()145     public boolean beginBatchEdit() {
146         return false;
147     }
148 
149     /**
150      * Default implementation does nothing.
151      */
endBatchEdit()152     public boolean endBatchEdit() {
153         return false;
154     }
155 
156     /**
157      * Default implementation calls {@link #finishComposingText()}.
158      */
159     @CallSuper
closeConnection()160     public void closeConnection() {
161         finishComposingText();
162     }
163 
164     /**
165      * Default implementation uses
166      * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
167      * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
168      */
clearMetaKeyStates(int states)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     /**
177      * Default implementation does nothing and returns false.
178      */
commitCompletion(CompletionInfo text)179     public boolean commitCompletion(CompletionInfo text) {
180         return false;
181     }
182 
183     /**
184      * Default implementation does nothing and returns false.
185      */
commitCorrection(CorrectionInfo correctionInfo)186     public boolean commitCorrection(CorrectionInfo correctionInfo) {
187         return false;
188     }
189 
190     /**
191      * 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      */
commitText(CharSequence text, int newCursorPosition)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 selection position of the
204      * editable text.
205      *
206      * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
207      *        If this is greater than the number of existing characters between the beginning of the
208      *        text and the cursor, then this method does not fail but deletes all the characters in
209      *        that range.
210      * @param afterLength The number of characters after the cursor to be deleted, in code unit.
211      *        If this is greater than the number of existing characters between the cursor and
212      *        the end of the text, then this method does not fail but deletes all the characters in
213      *        that range.
214      *
215      * @return {@code true} when selected text is deleted, {@code false} when either the
216      *         selection is invalid or not yet attached (i.e. selection start or end is -1),
217      *         or the editable text is {@code null}.
218      */
deleteSurroundingText(int beforeLength, int afterLength)219     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
220         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
221                 + " / " + afterLength);
222         final Editable content = getEditable();
223         if (content == null) return false;
224 
225         beginBatchEdit();
226 
227         int a = Selection.getSelectionStart(content);
228         int b = Selection.getSelectionEnd(content);
229 
230         if (a > b) {
231             int tmp = a;
232             a = b;
233             b = tmp;
234         }
235 
236         // Skip when the selection is not yet attached.
237         if (a == -1 || b == -1) {
238             endBatchEdit();
239             return false;
240         }
241 
242         // Ignore the composing text.
243         int ca = getComposingSpanStart(content);
244         int cb = getComposingSpanEnd(content);
245         if (cb < ca) {
246             int tmp = ca;
247             ca = cb;
248             cb = tmp;
249         }
250         if (ca != -1 && cb != -1) {
251             if (ca < a) a = ca;
252             if (cb > b) b = cb;
253         }
254 
255         int deleted = 0;
256 
257         if (beforeLength > 0) {
258             int start = a - beforeLength;
259             if (start < 0) start = 0;
260 
261             final int numDeleteBefore = a - start;
262             if (a >= 0 && numDeleteBefore > 0) {
263                 content.delete(start, a);
264                 deleted = numDeleteBefore;
265             }
266         }
267 
268         if (afterLength > 0) {
269             b = b - deleted;
270 
271             int end = b + afterLength;
272             if (end > content.length()) end = content.length();
273 
274             final int numDeleteAfter = end - b;
275             if (b >= 0 && numDeleteAfter > 0) {
276                 content.delete(b, end);
277             }
278         }
279 
280         endBatchEdit();
281 
282         return true;
283     }
284 
285     private static int INVALID_INDEX = -1;
findIndexBackward(final CharSequence cs, final int from, final int numCodePoints)286     private static int findIndexBackward(final CharSequence cs, final int from,
287             final int numCodePoints) {
288         int currentIndex = from;
289         boolean waitingHighSurrogate = false;
290         final int N = cs.length();
291         if (currentIndex < 0 || N < currentIndex) {
292             return INVALID_INDEX;  // The starting point is out of range.
293         }
294         if (numCodePoints < 0) {
295             return INVALID_INDEX;  // Basically this should not happen.
296         }
297         int remainingCodePoints = numCodePoints;
298         while (true) {
299             if (remainingCodePoints == 0) {
300                 return currentIndex;  // Reached to the requested length in code points.
301             }
302 
303             --currentIndex;
304             if (currentIndex < 0) {
305                 if (waitingHighSurrogate) {
306                     return INVALID_INDEX;  // An invalid surrogate pair is found.
307                 }
308                 return 0;  // Reached to the beginning of the text w/o any invalid surrogate pair.
309             }
310             final char c = cs.charAt(currentIndex);
311             if (waitingHighSurrogate) {
312                 if (!java.lang.Character.isHighSurrogate(c)) {
313                     return INVALID_INDEX;  // An invalid surrogate pair is found.
314                 }
315                 waitingHighSurrogate = false;
316                 --remainingCodePoints;
317                 continue;
318             }
319             if (!java.lang.Character.isSurrogate(c)) {
320                 --remainingCodePoints;
321                 continue;
322             }
323             if (java.lang.Character.isHighSurrogate(c)) {
324                 return INVALID_INDEX;  // A invalid surrogate pair is found.
325             }
326             waitingHighSurrogate = true;
327         }
328     }
329 
findIndexForward(final CharSequence cs, final int from, final int numCodePoints)330     private static int findIndexForward(final CharSequence cs, final int from,
331             final int numCodePoints) {
332         int currentIndex = from;
333         boolean waitingLowSurrogate = false;
334         final int N = cs.length();
335         if (currentIndex < 0 || N < currentIndex) {
336             return INVALID_INDEX;  // The starting point is out of range.
337         }
338         if (numCodePoints < 0) {
339             return INVALID_INDEX;  // Basically this should not happen.
340         }
341         int remainingCodePoints = numCodePoints;
342 
343         while (true) {
344             if (remainingCodePoints == 0) {
345                 return currentIndex;  // Reached to the requested length in code points.
346             }
347 
348             if (currentIndex >= N) {
349                 if (waitingLowSurrogate) {
350                     return INVALID_INDEX;  // An invalid surrogate pair is found.
351                 }
352                 return N;  // Reached to the end of the text w/o any invalid surrogate pair.
353             }
354             final char c = cs.charAt(currentIndex);
355             if (waitingLowSurrogate) {
356                 if (!java.lang.Character.isLowSurrogate(c)) {
357                     return INVALID_INDEX;  // An invalid surrogate pair is found.
358                 }
359                 --remainingCodePoints;
360                 waitingLowSurrogate = false;
361                 ++currentIndex;
362                 continue;
363             }
364             if (!java.lang.Character.isSurrogate(c)) {
365                 --remainingCodePoints;
366                 ++currentIndex;
367                 continue;
368             }
369             if (java.lang.Character.isLowSurrogate(c)) {
370                 return INVALID_INDEX;  // A invalid surrogate pair is found.
371             }
372             waitingLowSurrogate = true;
373             ++currentIndex;
374         }
375     }
376 
377     /**
378      * The default implementation performs the deletion around the current selection position of the
379      * editable text.
380      * @param beforeLength The number of characters before the cursor to be deleted, in code points.
381      *        If this is greater than the number of existing characters between the beginning of the
382      *        text and the cursor, then this method does not fail but deletes all the characters in
383      *        that range.
384      * @param afterLength The number of characters after the cursor to be deleted, in code points.
385      *        If this is greater than the number of existing characters between the cursor and
386      *        the end of the text, then this method does not fail but deletes all the characters in
387      *        that range.
388      */
deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)389     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
390         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
391                 + " / " + afterLength);
392         final Editable content = getEditable();
393         if (content == null) return false;
394 
395         beginBatchEdit();
396 
397         int a = Selection.getSelectionStart(content);
398         int b = Selection.getSelectionEnd(content);
399 
400         if (a > b) {
401             int tmp = a;
402             a = b;
403             b = tmp;
404         }
405 
406         // Ignore the composing text.
407         int ca = getComposingSpanStart(content);
408         int cb = getComposingSpanEnd(content);
409         if (cb < ca) {
410             int tmp = ca;
411             ca = cb;
412             cb = tmp;
413         }
414         if (ca != -1 && cb != -1) {
415             if (ca < a) a = ca;
416             if (cb > b) b = cb;
417         }
418 
419         if (a >= 0 && b >= 0) {
420             final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
421             if (start != INVALID_INDEX) {
422                 final int end = findIndexForward(content, b, Math.max(afterLength, 0));
423                 if (end != INVALID_INDEX) {
424                     final int numDeleteBefore = a - start;
425                     if (numDeleteBefore > 0) {
426                         content.delete(start, a);
427                     }
428                     final int numDeleteAfter = end - b;
429                     if (numDeleteAfter > 0) {
430                         content.delete(b - numDeleteBefore, end - numDeleteBefore);
431                     }
432                 }
433             }
434             // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
435             // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
436             // always returns true to the IME without waiting for the completion of this method as
437             // IInputConnectionWrapper#isAtive() returns true.  This is actually why some methods
438             // including this method look like asynchronous calls from the IME.
439         }
440 
441         endBatchEdit();
442 
443         return true;
444     }
445 
446     /**
447      * The default implementation removes the composing state from the
448      * current editable text.  In addition, only if dummy mode, a key event is
449      * sent for the new text and the current editable buffer cleared.
450      */
finishComposingText()451     public boolean finishComposingText() {
452         if (DEBUG) Log.v(TAG, "finishComposingText");
453         final Editable content = getEditable();
454         if (content != null) {
455             beginBatchEdit();
456             removeComposingSpans(content);
457             // Note: sendCurrentText does nothing unless mDummyMode is set
458             sendCurrentText();
459             endBatchEdit();
460         }
461         return true;
462     }
463 
464     /**
465      * The default implementation uses TextUtils.getCapsMode to get the
466      * cursor caps mode for the current selection position in the editable
467      * text, unless in dummy mode in which case 0 is always returned.
468      */
getCursorCapsMode(int reqModes)469     public int getCursorCapsMode(int reqModes) {
470         if (mDummyMode) return 0;
471 
472         final Editable content = getEditable();
473         if (content == null) return 0;
474 
475         int a = Selection.getSelectionStart(content);
476         int b = Selection.getSelectionEnd(content);
477 
478         if (a > b) {
479             int tmp = a;
480             a = b;
481             b = tmp;
482         }
483 
484         return TextUtils.getCapsMode(content, a, reqModes);
485     }
486 
487     /**
488      * The default implementation always returns null.
489      */
getExtractedText(ExtractedTextRequest request, int flags)490     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
491         return null;
492     }
493 
494     /**
495      * The default implementation returns the given amount of text from the
496      * current cursor position in the buffer.
497      */
getTextBeforeCursor(int length, int flags)498     public CharSequence getTextBeforeCursor(int length, int flags) {
499         final Editable content = getEditable();
500         if (content == null) return null;
501 
502         int a = Selection.getSelectionStart(content);
503         int b = Selection.getSelectionEnd(content);
504 
505         if (a > b) {
506             int tmp = a;
507             a = b;
508             b = tmp;
509         }
510 
511         if (a <= 0) {
512             return "";
513         }
514 
515         if (length > a) {
516             length = a;
517         }
518 
519         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
520             return content.subSequence(a - length, a);
521         }
522         return TextUtils.substring(content, a - length, a);
523     }
524 
525     /**
526      * The default implementation returns the text currently selected, or null if none is
527      * selected.
528      */
getSelectedText(int flags)529     public CharSequence getSelectedText(int flags) {
530         final Editable content = getEditable();
531         if (content == null) return null;
532 
533         int a = Selection.getSelectionStart(content);
534         int b = Selection.getSelectionEnd(content);
535 
536         if (a > b) {
537             int tmp = a;
538             a = b;
539             b = tmp;
540         }
541 
542         if (a == b || a < 0) return null;
543 
544         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
545             return content.subSequence(a, b);
546         }
547         return TextUtils.substring(content, a, b);
548     }
549 
550     /**
551      * The default implementation returns the given amount of text from the
552      * current cursor position in the buffer.
553      */
getTextAfterCursor(int length, int flags)554     public CharSequence getTextAfterCursor(int length, int flags) {
555         final Editable content = getEditable();
556         if (content == null) return null;
557 
558         int a = Selection.getSelectionStart(content);
559         int b = Selection.getSelectionEnd(content);
560 
561         if (a > b) {
562             int tmp = a;
563             a = b;
564             b = tmp;
565         }
566 
567         // Guard against the case where the cursor has not been positioned yet.
568         if (b < 0) {
569             b = 0;
570         }
571 
572         if (b + length > content.length()) {
573             length = content.length() - b;
574         }
575 
576 
577         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
578             return content.subSequence(b, b + length);
579         }
580         return TextUtils.substring(content, b, b + length);
581     }
582 
583     /**
584      * The default implementation turns this into the enter key.
585      */
performEditorAction(int actionCode)586     public boolean performEditorAction(int actionCode) {
587         long eventTime = SystemClock.uptimeMillis();
588         sendKeyEvent(new KeyEvent(eventTime, eventTime,
589                 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
590                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
591                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
592                 | KeyEvent.FLAG_EDITOR_ACTION));
593         sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
594                 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
595                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
596                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
597                 | KeyEvent.FLAG_EDITOR_ACTION));
598         return true;
599     }
600 
601     /**
602      * The default implementation does nothing.
603      */
performContextMenuAction(int id)604     public boolean performContextMenuAction(int id) {
605         return false;
606     }
607 
608     /**
609      * The default implementation does nothing.
610      */
performPrivateCommand(String action, Bundle data)611     public boolean performPrivateCommand(String action, Bundle data) {
612         return false;
613     }
614 
615     /**
616      * The default implementation does nothing.
617      */
requestCursorUpdates(int cursorUpdateMode)618     public boolean requestCursorUpdates(int cursorUpdateMode) {
619         return false;
620     }
621 
getHandler()622     public Handler getHandler() {
623         return null;
624     }
625 
626     /**
627      * The default implementation places the given text into the editable,
628      * replacing any existing composing text.  The new text is marked as
629      * in a composing state with the composing style.
630      */
setComposingText(CharSequence text, int newCursorPosition)631     public boolean setComposingText(CharSequence text, int newCursorPosition) {
632         if (DEBUG) Log.v(TAG, "setComposingText " + text);
633         replaceText(text, newCursorPosition, true);
634         return true;
635     }
636 
setComposingRegion(int start, int end)637     public boolean setComposingRegion(int start, int end) {
638         final Editable content = getEditable();
639         if (content != null) {
640             beginBatchEdit();
641             removeComposingSpans(content);
642             int a = start;
643             int b = end;
644             if (a > b) {
645                 int tmp = a;
646                 a = b;
647                 b = tmp;
648             }
649             // Clip the end points to be within the content bounds.
650             final int length = content.length();
651             if (a < 0) a = 0;
652             if (b < 0) b = 0;
653             if (a > length) a = length;
654             if (b > length) b = length;
655 
656             ensureDefaultComposingSpans();
657             if (mDefaultComposingSpans != null) {
658                 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
659                     content.setSpan(mDefaultComposingSpans[i], a, b,
660                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
661                 }
662             }
663 
664             content.setSpan(COMPOSING, a, b,
665                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
666 
667             // Note: sendCurrentText does nothing unless mDummyMode is set
668             sendCurrentText();
669             endBatchEdit();
670         }
671         return true;
672     }
673 
674     /**
675      * The default implementation changes the selection position in the
676      * current editable text.
677      */
setSelection(int start, int end)678     public boolean setSelection(int start, int end) {
679         if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
680         final Editable content = getEditable();
681         if (content == null) return false;
682         int len = content.length();
683         if (start > len || end > len || start < 0 || end < 0) {
684             // If the given selection is out of bounds, just ignore it.
685             // Most likely the text was changed out from under the IME,
686             // and the IME is going to have to update all of its state
687             // anyway.
688             return true;
689         }
690         if (start == end && MetaKeyKeyListener.getMetaState(content,
691                 MetaKeyKeyListener.META_SELECTING) != 0) {
692             // If we are in selection mode, then we want to extend the
693             // selection instead of replacing it.
694             Selection.extendSelection(content, start);
695         } else {
696             Selection.setSelection(content, start, end);
697         }
698         return true;
699     }
700 
701     /**
702      * Provides standard implementation for sending a key event to the window
703      * attached to the input connection's view.
704      */
sendKeyEvent(KeyEvent event)705     public boolean sendKeyEvent(KeyEvent event) {
706         mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
707         return false;
708     }
709 
710     /**
711      * Updates InputMethodManager with the current fullscreen mode.
712      */
reportFullscreenMode(boolean enabled)713     public boolean reportFullscreenMode(boolean enabled) {
714         return true;
715     }
716 
sendCurrentText()717     private void sendCurrentText() {
718         if (!mDummyMode) {
719             return;
720         }
721 
722         Editable content = getEditable();
723         if (content != null) {
724             final int N = content.length();
725             if (N == 0) {
726                 return;
727             }
728             if (N == 1) {
729                 // If it's 1 character, we have a chance of being
730                 // able to generate normal key events...
731                 if (mKeyCharacterMap == null) {
732                     mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
733                 }
734                 char[] chars = new char[1];
735                 content.getChars(0, 1, chars, 0);
736                 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
737                 if (events != null) {
738                     for (int i=0; i<events.length; i++) {
739                         if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
740                         sendKeyEvent(events[i]);
741                     }
742                     content.clear();
743                     return;
744                 }
745             }
746 
747             // Otherwise, revert to the special key event containing
748             // the actual characters.
749             KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
750                     content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
751             sendKeyEvent(event);
752             content.clear();
753         }
754     }
755 
ensureDefaultComposingSpans()756     private void ensureDefaultComposingSpans() {
757         if (mDefaultComposingSpans == null) {
758             Context context;
759             if (mTargetView != null) {
760                 context = mTargetView.getContext();
761             } else if (mIMM.mCurRootView != null) {
762                 final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView();
763                 context = servedView != null ? servedView.getContext() : null;
764             } else {
765                 context = null;
766             }
767             if (context != null) {
768                 TypedArray ta = context.getTheme()
769                         .obtainStyledAttributes(new int[] {
770                                 com.android.internal.R.attr.candidatesTextStyleSpans
771                         });
772                 CharSequence style = ta.getText(0);
773                 ta.recycle();
774                 if (style != null && style instanceof Spanned) {
775                     mDefaultComposingSpans = ((Spanned)style).getSpans(
776                             0, style.length(), Object.class);
777                 }
778             }
779         }
780     }
781 
replaceText(CharSequence text, int newCursorPosition, boolean composing)782     private void replaceText(CharSequence text, int newCursorPosition,
783             boolean composing) {
784         final Editable content = getEditable();
785         if (content == null) {
786             return;
787         }
788 
789         beginBatchEdit();
790 
791         // delete composing text set previously.
792         int a = getComposingSpanStart(content);
793         int b = getComposingSpanEnd(content);
794 
795         if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
796 
797         if (b < a) {
798             int tmp = a;
799             a = b;
800             b = tmp;
801         }
802 
803         if (a != -1 && b != -1) {
804             removeComposingSpans(content);
805         } else {
806             a = Selection.getSelectionStart(content);
807             b = Selection.getSelectionEnd(content);
808             if (a < 0) a = 0;
809             if (b < 0) b = 0;
810             if (b < a) {
811                 int tmp = a;
812                 a = b;
813                 b = tmp;
814             }
815         }
816 
817         if (composing) {
818             Spannable sp = null;
819             if (!(text instanceof Spannable)) {
820                 sp = new SpannableStringBuilder(text);
821                 text = sp;
822                 ensureDefaultComposingSpans();
823                 if (mDefaultComposingSpans != null) {
824                     for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
825                         sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
826                                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
827                     }
828                 }
829             } else {
830                 sp = (Spannable)text;
831             }
832             setComposingSpans(sp);
833         }
834 
835         if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
836                 + text + "\", composing=" + composing
837                 + ", type=" + text.getClass().getCanonicalName());
838 
839         if (DEBUG) {
840             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
841             lp.println("Current text:");
842             TextUtils.dumpSpans(content, lp, "  ");
843             lp.println("Composing text:");
844             TextUtils.dumpSpans(text, lp, "  ");
845         }
846 
847         // Position the cursor appropriately, so that after replacing the
848         // desired range of text it will be located in the correct spot.
849         // This allows us to deal with filters performing edits on the text
850         // we are providing here.
851         if (newCursorPosition > 0) {
852             newCursorPosition += b - 1;
853         } else {
854             newCursorPosition += a;
855         }
856         if (newCursorPosition < 0) newCursorPosition = 0;
857         if (newCursorPosition > content.length())
858             newCursorPosition = content.length();
859         Selection.setSelection(content, newCursorPosition);
860 
861         content.replace(a, b, text);
862 
863         if (DEBUG) {
864             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
865             lp.println("Final text:");
866             TextUtils.dumpSpans(content, lp, "  ");
867         }
868 
869         endBatchEdit();
870     }
871 
872     /**
873      * The default implementation does nothing.
874      */
commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)875     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
876         return false;
877     }
878 }
879