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