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.content.Context;
20 import android.content.res.TypedArray;
21 import android.os.Bundle;
22 import android.os.SystemClock;
23 import android.text.Editable;
24 import android.text.NoCopySpan;
25 import android.text.Selection;
26 import android.text.Spannable;
27 import android.text.SpannableStringBuilder;
28 import android.text.Spanned;
29 import android.text.TextUtils;
30 import android.text.method.MetaKeyKeyListener;
31 import android.util.Log;
32 import android.util.LogPrinter;
33 import android.view.KeyCharacterMap;
34 import android.view.KeyEvent;
35 import android.view.View;
36 import android.view.ViewRootImpl;
37 
38 class ComposingText implements NoCopySpan {
39 }
40 
41 /**
42  * Base class for implementors of the InputConnection interface, taking care
43  * of most of the common behavior for providing a connection to an Editable.
44  * Implementors of this class will want to be sure to implement
45  * {@link #getEditable} to provide access to their own editable object, and
46  * to refer to the documentation in {@link InputConnection}.
47  */
48 public class BaseInputConnection implements InputConnection {
49     private static final boolean DEBUG = false;
50     private static final String TAG = "BaseInputConnection";
51     static final Object COMPOSING = new ComposingText();
52 
53     /** @hide */
54     protected final InputMethodManager mIMM;
55     final View mTargetView;
56     final boolean mDummyMode;
57 
58     private Object[] mDefaultComposingSpans;
59 
60     Editable mEditable;
61     KeyCharacterMap mKeyCharacterMap;
62 
BaseInputConnection(InputMethodManager mgr, boolean fullEditor)63     BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
64         mIMM = mgr;
65         mTargetView = null;
66         mDummyMode = !fullEditor;
67     }
68 
BaseInputConnection(View targetView, boolean fullEditor)69     public BaseInputConnection(View targetView, boolean fullEditor) {
70         mIMM = (InputMethodManager)targetView.getContext().getSystemService(
71                 Context.INPUT_METHOD_SERVICE);
72         mTargetView = targetView;
73         mDummyMode = !fullEditor;
74     }
75 
removeComposingSpans(Spannable text)76     public static final void removeComposingSpans(Spannable text) {
77         text.removeSpan(COMPOSING);
78         Object[] sps = text.getSpans(0, text.length(), Object.class);
79         if (sps != null) {
80             for (int i=sps.length-1; i>=0; i--) {
81                 Object o = sps[i];
82                 if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
83                     text.removeSpan(o);
84                 }
85             }
86         }
87     }
88 
setComposingSpans(Spannable text)89     public static void setComposingSpans(Spannable text) {
90         setComposingSpans(text, 0, text.length());
91     }
92 
93     /** @hide */
setComposingSpans(Spannable text, int start, int end)94     public static void setComposingSpans(Spannable text, int start, int end) {
95         final Object[] sps = text.getSpans(start, end, Object.class);
96         if (sps != null) {
97             for (int i=sps.length-1; i>=0; i--) {
98                 final Object o = sps[i];
99                 if (o == COMPOSING) {
100                     text.removeSpan(o);
101                     continue;
102                 }
103 
104                 final int fl = text.getSpanFlags(o);
105                 if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
106                         != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
107                     text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
108                             (fl & ~Spanned.SPAN_POINT_MARK_MASK)
109                                     | Spanned.SPAN_COMPOSING
110                                     | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
111                 }
112             }
113         }
114 
115         text.setSpan(COMPOSING, start, end,
116                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
117     }
118 
getComposingSpanStart(Spannable text)119     public static int getComposingSpanStart(Spannable text) {
120         return text.getSpanStart(COMPOSING);
121     }
122 
getComposingSpanEnd(Spannable text)123     public static int getComposingSpanEnd(Spannable text) {
124         return text.getSpanEnd(COMPOSING);
125     }
126 
127     /**
128      * Return the target of edit operations.  The default implementation
129      * returns its own fake editable that is just used for composing text;
130      * subclasses that are real text editors should override this and
131      * supply their own.
132      */
getEditable()133     public Editable getEditable() {
134         if (mEditable == null) {
135             mEditable = Editable.Factory.getInstance().newEditable("");
136             Selection.setSelection(mEditable, 0);
137         }
138         return mEditable;
139     }
140 
141     /**
142      * Default implementation does nothing.
143      */
beginBatchEdit()144     public boolean beginBatchEdit() {
145         return false;
146     }
147 
148     /**
149      * Default implementation does nothing.
150      */
endBatchEdit()151     public boolean endBatchEdit() {
152         return false;
153     }
154 
155     /**
156      * Called when this InputConnection is no longer used by the InputMethodManager.
157      *
158      * @hide
159      */
reportFinish()160     protected void reportFinish() {
161         // Intentionaly empty
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         mIMM.notifyUserAction();
199         sendCurrentText();
200         return true;
201     }
202 
203     /**
204      * The default implementation performs the deletion around the current
205      * selection position of the editable text.
206      * @param beforeLength
207      * @param afterLength
208      */
deleteSurroundingText(int beforeLength, int afterLength)209     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
210         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
211                 + " / " + afterLength);
212         final Editable content = getEditable();
213         if (content == null) return false;
214 
215         beginBatchEdit();
216 
217         int a = Selection.getSelectionStart(content);
218         int b = Selection.getSelectionEnd(content);
219 
220         if (a > b) {
221             int tmp = a;
222             a = b;
223             b = tmp;
224         }
225 
226         // ignore the composing text.
227         int ca = getComposingSpanStart(content);
228         int cb = getComposingSpanEnd(content);
229         if (cb < ca) {
230             int tmp = ca;
231             ca = cb;
232             cb = tmp;
233         }
234         if (ca != -1 && cb != -1) {
235             if (ca < a) a = ca;
236             if (cb > b) b = cb;
237         }
238 
239         int deleted = 0;
240 
241         if (beforeLength > 0) {
242             int start = a - beforeLength;
243             if (start < 0) start = 0;
244             content.delete(start, a);
245             deleted = a - start;
246         }
247 
248         if (afterLength > 0) {
249             b = b - deleted;
250 
251             int end = b + afterLength;
252             if (end > content.length()) end = content.length();
253 
254             content.delete(b, end);
255         }
256 
257         endBatchEdit();
258 
259         return true;
260     }
261 
262     /**
263      * The default implementation removes the composing state from the
264      * current editable text.  In addition, only if dummy mode, a key event is
265      * sent for the new text and the current editable buffer cleared.
266      */
finishComposingText()267     public boolean finishComposingText() {
268         if (DEBUG) Log.v(TAG, "finishComposingText");
269         final Editable content = getEditable();
270         if (content != null) {
271             beginBatchEdit();
272             removeComposingSpans(content);
273             // Note: sendCurrentText does nothing unless mDummyMode is set
274             sendCurrentText();
275             endBatchEdit();
276         }
277         return true;
278     }
279 
280     /**
281      * The default implementation uses TextUtils.getCapsMode to get the
282      * cursor caps mode for the current selection position in the editable
283      * text, unless in dummy mode in which case 0 is always returned.
284      */
getCursorCapsMode(int reqModes)285     public int getCursorCapsMode(int reqModes) {
286         if (mDummyMode) return 0;
287 
288         final Editable content = getEditable();
289         if (content == null) return 0;
290 
291         int a = Selection.getSelectionStart(content);
292         int b = Selection.getSelectionEnd(content);
293 
294         if (a > b) {
295             int tmp = a;
296             a = b;
297             b = tmp;
298         }
299 
300         return TextUtils.getCapsMode(content, a, reqModes);
301     }
302 
303     /**
304      * The default implementation always returns null.
305      */
getExtractedText(ExtractedTextRequest request, int flags)306     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
307         return null;
308     }
309 
310     /**
311      * The default implementation returns the given amount of text from the
312      * current cursor position in the buffer.
313      */
getTextBeforeCursor(int length, int flags)314     public CharSequence getTextBeforeCursor(int length, int flags) {
315         final Editable content = getEditable();
316         if (content == null) return null;
317 
318         int a = Selection.getSelectionStart(content);
319         int b = Selection.getSelectionEnd(content);
320 
321         if (a > b) {
322             int tmp = a;
323             a = b;
324             b = tmp;
325         }
326 
327         if (a <= 0) {
328             return "";
329         }
330 
331         if (length > a) {
332             length = a;
333         }
334 
335         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
336             return content.subSequence(a - length, a);
337         }
338         return TextUtils.substring(content, a - length, a);
339     }
340 
341     /**
342      * The default implementation returns the text currently selected, or null if none is
343      * selected.
344      */
getSelectedText(int flags)345     public CharSequence getSelectedText(int flags) {
346         final Editable content = getEditable();
347         if (content == null) return null;
348 
349         int a = Selection.getSelectionStart(content);
350         int b = Selection.getSelectionEnd(content);
351 
352         if (a > b) {
353             int tmp = a;
354             a = b;
355             b = tmp;
356         }
357 
358         if (a == b) return null;
359 
360         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
361             return content.subSequence(a, b);
362         }
363         return TextUtils.substring(content, a, b);
364     }
365 
366     /**
367      * The default implementation returns the given amount of text from the
368      * current cursor position in the buffer.
369      */
getTextAfterCursor(int length, int flags)370     public CharSequence getTextAfterCursor(int length, int flags) {
371         final Editable content = getEditable();
372         if (content == null) return null;
373 
374         int a = Selection.getSelectionStart(content);
375         int b = Selection.getSelectionEnd(content);
376 
377         if (a > b) {
378             int tmp = a;
379             a = b;
380             b = tmp;
381         }
382 
383         // Guard against the case where the cursor has not been positioned yet.
384         if (b < 0) {
385             b = 0;
386         }
387 
388         if (b + length > content.length()) {
389             length = content.length() - b;
390         }
391 
392 
393         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
394             return content.subSequence(b, b + length);
395         }
396         return TextUtils.substring(content, b, b + length);
397     }
398 
399     /**
400      * The default implementation turns this into the enter key.
401      */
performEditorAction(int actionCode)402     public boolean performEditorAction(int actionCode) {
403         long eventTime = SystemClock.uptimeMillis();
404         sendKeyEvent(new KeyEvent(eventTime, eventTime,
405                 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
406                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
407                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
408                 | KeyEvent.FLAG_EDITOR_ACTION));
409         sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
410                 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
411                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
412                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
413                 | KeyEvent.FLAG_EDITOR_ACTION));
414         return true;
415     }
416 
417     /**
418      * The default implementation does nothing.
419      */
performContextMenuAction(int id)420     public boolean performContextMenuAction(int id) {
421         return false;
422     }
423 
424     /**
425      * The default implementation does nothing.
426      */
performPrivateCommand(String action, Bundle data)427     public boolean performPrivateCommand(String action, Bundle data) {
428         return false;
429     }
430 
431     /**
432      * The default implementation does nothing.
433      */
requestCursorUpdates(int cursorUpdateMode)434     public boolean requestCursorUpdates(int cursorUpdateMode) {
435         return false;
436     }
437 
438     /**
439      * The default implementation places the given text into the editable,
440      * replacing any existing composing text.  The new text is marked as
441      * in a composing state with the composing style.
442      */
setComposingText(CharSequence text, int newCursorPosition)443     public boolean setComposingText(CharSequence text, int newCursorPosition) {
444         if (DEBUG) Log.v(TAG, "setComposingText " + text);
445         replaceText(text, newCursorPosition, true);
446         mIMM.notifyUserAction();
447         return true;
448     }
449 
setComposingRegion(int start, int end)450     public boolean setComposingRegion(int start, int end) {
451         final Editable content = getEditable();
452         if (content != null) {
453             beginBatchEdit();
454             removeComposingSpans(content);
455             int a = start;
456             int b = end;
457             if (a > b) {
458                 int tmp = a;
459                 a = b;
460                 b = tmp;
461             }
462             // Clip the end points to be within the content bounds.
463             final int length = content.length();
464             if (a < 0) a = 0;
465             if (b < 0) b = 0;
466             if (a > length) a = length;
467             if (b > length) b = length;
468 
469             ensureDefaultComposingSpans();
470             if (mDefaultComposingSpans != null) {
471                 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
472                     content.setSpan(mDefaultComposingSpans[i], a, b,
473                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
474                 }
475             }
476 
477             content.setSpan(COMPOSING, a, b,
478                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
479 
480             // Note: sendCurrentText does nothing unless mDummyMode is set
481             sendCurrentText();
482             endBatchEdit();
483         }
484         return true;
485     }
486 
487     /**
488      * The default implementation changes the selection position in the
489      * current editable text.
490      */
setSelection(int start, int end)491     public boolean setSelection(int start, int end) {
492         if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
493         final Editable content = getEditable();
494         if (content == null) return false;
495         int len = content.length();
496         if (start > len || end > len || start < 0 || end < 0) {
497             // If the given selection is out of bounds, just ignore it.
498             // Most likely the text was changed out from under the IME,
499             // and the IME is going to have to update all of its state
500             // anyway.
501             return true;
502         }
503         if (start == end && MetaKeyKeyListener.getMetaState(content,
504                 MetaKeyKeyListener.META_SELECTING) != 0) {
505             // If we are in selection mode, then we want to extend the
506             // selection instead of replacing it.
507             Selection.extendSelection(content, start);
508         } else {
509             Selection.setSelection(content, start, end);
510         }
511         return true;
512     }
513 
514     /**
515      * Provides standard implementation for sending a key event to the window
516      * attached to the input connection's view.
517      */
sendKeyEvent(KeyEvent event)518     public boolean sendKeyEvent(KeyEvent event) {
519         synchronized (mIMM.mH) {
520             ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
521             if (viewRootImpl == null) {
522                 if (mIMM.mServedView != null) {
523                     viewRootImpl = mIMM.mServedView.getViewRootImpl();
524                 }
525             }
526             if (viewRootImpl != null) {
527                 viewRootImpl.dispatchKeyFromIme(event);
528             }
529         }
530         mIMM.notifyUserAction();
531         return false;
532     }
533 
534     /**
535      * Updates InputMethodManager with the current fullscreen mode.
536      */
reportFullscreenMode(boolean enabled)537     public boolean reportFullscreenMode(boolean enabled) {
538         mIMM.setFullscreenMode(enabled);
539         return true;
540     }
541 
sendCurrentText()542     private void sendCurrentText() {
543         if (!mDummyMode) {
544             return;
545         }
546 
547         Editable content = getEditable();
548         if (content != null) {
549             final int N = content.length();
550             if (N == 0) {
551                 return;
552             }
553             if (N == 1) {
554                 // If it's 1 character, we have a chance of being
555                 // able to generate normal key events...
556                 if (mKeyCharacterMap == null) {
557                     mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
558                 }
559                 char[] chars = new char[1];
560                 content.getChars(0, 1, chars, 0);
561                 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
562                 if (events != null) {
563                     for (int i=0; i<events.length; i++) {
564                         if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
565                         sendKeyEvent(events[i]);
566                     }
567                     content.clear();
568                     return;
569                 }
570             }
571 
572             // Otherwise, revert to the special key event containing
573             // the actual characters.
574             KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
575                     content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
576             sendKeyEvent(event);
577             content.clear();
578         }
579     }
580 
ensureDefaultComposingSpans()581     private void ensureDefaultComposingSpans() {
582         if (mDefaultComposingSpans == null) {
583             Context context;
584             if (mTargetView != null) {
585                 context = mTargetView.getContext();
586             } else if (mIMM.mServedView != null) {
587                 context = mIMM.mServedView.getContext();
588             } else {
589                 context = null;
590             }
591             if (context != null) {
592                 TypedArray ta = context.getTheme()
593                         .obtainStyledAttributes(new int[] {
594                                 com.android.internal.R.attr.candidatesTextStyleSpans
595                         });
596                 CharSequence style = ta.getText(0);
597                 ta.recycle();
598                 if (style != null && style instanceof Spanned) {
599                     mDefaultComposingSpans = ((Spanned)style).getSpans(
600                             0, style.length(), Object.class);
601                 }
602             }
603         }
604     }
605 
replaceText(CharSequence text, int newCursorPosition, boolean composing)606     private void replaceText(CharSequence text, int newCursorPosition,
607             boolean composing) {
608         final Editable content = getEditable();
609         if (content == null) {
610             return;
611         }
612 
613         beginBatchEdit();
614 
615         // delete composing text set previously.
616         int a = getComposingSpanStart(content);
617         int b = getComposingSpanEnd(content);
618 
619         if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
620 
621         if (b < a) {
622             int tmp = a;
623             a = b;
624             b = tmp;
625         }
626 
627         if (a != -1 && b != -1) {
628             removeComposingSpans(content);
629         } else {
630             a = Selection.getSelectionStart(content);
631             b = Selection.getSelectionEnd(content);
632             if (a < 0) a = 0;
633             if (b < 0) b = 0;
634             if (b < a) {
635                 int tmp = a;
636                 a = b;
637                 b = tmp;
638             }
639         }
640 
641         if (composing) {
642             Spannable sp = null;
643             if (!(text instanceof Spannable)) {
644                 sp = new SpannableStringBuilder(text);
645                 text = sp;
646                 ensureDefaultComposingSpans();
647                 if (mDefaultComposingSpans != null) {
648                     for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
649                         sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
650                                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
651                     }
652                 }
653             } else {
654                 sp = (Spannable)text;
655             }
656             setComposingSpans(sp);
657         }
658 
659         if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
660                 + text + "\", composing=" + composing
661                 + ", type=" + text.getClass().getCanonicalName());
662 
663         if (DEBUG) {
664             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
665             lp.println("Current text:");
666             TextUtils.dumpSpans(content, lp, "  ");
667             lp.println("Composing text:");
668             TextUtils.dumpSpans(text, lp, "  ");
669         }
670 
671         // Position the cursor appropriately, so that after replacing the
672         // desired range of text it will be located in the correct spot.
673         // This allows us to deal with filters performing edits on the text
674         // we are providing here.
675         if (newCursorPosition > 0) {
676             newCursorPosition += b - 1;
677         } else {
678             newCursorPosition += a;
679         }
680         if (newCursorPosition < 0) newCursorPosition = 0;
681         if (newCursorPosition > content.length())
682             newCursorPosition = content.length();
683         Selection.setSelection(content, newCursorPosition);
684 
685         content.replace(a, b, text);
686 
687         if (DEBUG) {
688             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
689             lp.println("Final text:");
690             TextUtils.dumpSpans(content, lp, "  ");
691         }
692 
693         endBatchEdit();
694     }
695 }
696