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