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