BaseInputConnection.java revision c6cc0f8c19d9eccf408a443fa2bf668af261dcd0
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
17package android.view.inputmethod;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.os.Bundle;
22import android.os.Handler;
23import android.os.SystemClock;
24import android.text.Editable;
25import android.text.NoCopySpan;
26import android.text.Selection;
27import android.text.Spannable;
28import android.text.SpannableStringBuilder;
29import android.text.Spanned;
30import android.text.TextUtils;
31import android.text.method.MetaKeyKeyListener;
32import android.util.Log;
33import android.util.LogPrinter;
34import android.view.KeyCharacterMap;
35import android.view.KeyEvent;
36import android.view.View;
37import android.view.ViewAncestor;
38
39class 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 */
48public 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
62    BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
63        mIMM = mgr;
64        mTargetView = null;
65        mDummyMode = !fullEditor;
66    }
67
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
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
88    public static void setComposingSpans(Spannable text) {
89        setComposingSpans(text, 0, text.length());
90    }
91
92    /** @hide */
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
118    public static int getComposingSpanStart(Spannable text) {
119        return text.getSpanStart(COMPOSING);
120    }
121
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     */
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     */
143    public boolean beginBatchEdit() {
144        return false;
145    }
146
147    /**
148     * Default implementation does nothing.
149     */
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     */
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 and returns false.
168     */
169    public boolean commitCompletion(CompletionInfo text) {
170        return false;
171    }
172
173    /**
174     * Default implementation does nothing and returns false.
175     */
176    public boolean commitCorrection(CorrectionInfo correctionInfo) {
177        return false;
178    }
179
180    /**
181     * Default implementation replaces any existing composing text with
182     * the given text.  In addition, only if dummy mode, a key event is
183     * sent for the new text and the current editable buffer cleared.
184     */
185    public boolean commitText(CharSequence text, int newCursorPosition) {
186        if (DEBUG) Log.v(TAG, "commitText " + text);
187        replaceText(text, newCursorPosition, false);
188        sendCurrentText();
189        return true;
190    }
191
192    /**
193     * The default implementation performs the deletion around the current
194     * selection position of the editable text.
195     */
196    public boolean deleteSurroundingText(int leftLength, int rightLength) {
197        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
198                + " / " + rightLength);
199        final Editable content = getEditable();
200        if (content == null) return false;
201
202        beginBatchEdit();
203
204        int a = Selection.getSelectionStart(content);
205        int b = Selection.getSelectionEnd(content);
206
207        if (a > b) {
208            int tmp = a;
209            a = b;
210            b = tmp;
211        }
212
213        // ignore the composing text.
214        int ca = getComposingSpanStart(content);
215        int cb = getComposingSpanEnd(content);
216        if (cb < ca) {
217            int tmp = ca;
218            ca = cb;
219            cb = tmp;
220        }
221        if (ca != -1 && cb != -1) {
222            if (ca < a) a = ca;
223            if (cb > b) b = cb;
224        }
225
226        int deleted = 0;
227
228        if (leftLength > 0) {
229            int start = a - leftLength;
230            if (start < 0) start = 0;
231            content.delete(start, a);
232            deleted = a - start;
233        }
234
235        if (rightLength > 0) {
236            b = b - deleted;
237
238            int end = b + rightLength;
239            if (end > content.length()) end = content.length();
240
241            content.delete(b, end);
242        }
243
244        endBatchEdit();
245
246        return true;
247    }
248
249    /**
250     * The default implementation removes the composing state from the
251     * current editable text.  In addition, only if dummy mode, a key event is
252     * sent for the new text and the current editable buffer cleared.
253     */
254    public boolean finishComposingText() {
255        if (DEBUG) Log.v(TAG, "finishComposingText");
256        final Editable content = getEditable();
257        if (content != null) {
258            beginBatchEdit();
259            removeComposingSpans(content);
260            endBatchEdit();
261            sendCurrentText();
262        }
263        return true;
264    }
265
266    /**
267     * The default implementation uses TextUtils.getCapsMode to get the
268     * cursor caps mode for the current selection position in the editable
269     * text, unless in dummy mode in which case 0 is always returned.
270     */
271    public int getCursorCapsMode(int reqModes) {
272        if (mDummyMode) return 0;
273
274        final Editable content = getEditable();
275        if (content == null) return 0;
276
277        int a = Selection.getSelectionStart(content);
278        int b = Selection.getSelectionEnd(content);
279
280        if (a > b) {
281            int tmp = a;
282            a = b;
283            b = tmp;
284        }
285
286        return TextUtils.getCapsMode(content, a, reqModes);
287    }
288
289    /**
290     * The default implementation always returns null.
291     */
292    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
293        return null;
294    }
295
296    /**
297     * The default implementation returns the given amount of text from the
298     * current cursor position in the buffer.
299     */
300    public CharSequence getTextBeforeCursor(int length, int flags) {
301        final Editable content = getEditable();
302        if (content == null) return null;
303
304        int a = Selection.getSelectionStart(content);
305        int b = Selection.getSelectionEnd(content);
306
307        if (a > b) {
308            int tmp = a;
309            a = b;
310            b = tmp;
311        }
312
313        if (a <= 0) {
314            return "";
315        }
316
317        if (length > a) {
318            length = a;
319        }
320
321        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
322            return content.subSequence(a - length, a);
323        }
324        return TextUtils.substring(content, a - length, a);
325    }
326
327    /**
328     * The default implementation returns the text currently selected, or null if none is
329     * selected.
330     */
331    public CharSequence getSelectedText(int flags) {
332        final Editable content = getEditable();
333        if (content == null) return null;
334
335        int a = Selection.getSelectionStart(content);
336        int b = Selection.getSelectionEnd(content);
337
338        if (a > b) {
339            int tmp = a;
340            a = b;
341            b = tmp;
342        }
343
344        if (a == b) return null;
345
346        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
347            return content.subSequence(a, b);
348        }
349        return TextUtils.substring(content, a, b);
350    }
351
352    /**
353     * The default implementation returns the given amount of text from the
354     * current cursor position in the buffer.
355     */
356    public CharSequence getTextAfterCursor(int length, int flags) {
357        final Editable content = getEditable();
358        if (content == null) return null;
359
360        int a = Selection.getSelectionStart(content);
361        int b = Selection.getSelectionEnd(content);
362
363        if (a > b) {
364            int tmp = a;
365            a = b;
366            b = tmp;
367        }
368
369        // Guard against the case where the cursor has not been positioned yet.
370        if (b < 0) {
371            b = 0;
372        }
373
374        if (b + length > content.length()) {
375            length = content.length() - b;
376        }
377
378
379        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
380            return content.subSequence(b, b + length);
381        }
382        return TextUtils.substring(content, b, b + length);
383    }
384
385    /**
386     * The default implementation turns this into the enter key.
387     */
388    public boolean performEditorAction(int actionCode) {
389        long eventTime = SystemClock.uptimeMillis();
390        sendKeyEvent(new KeyEvent(eventTime, eventTime,
391                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
392                KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
393                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
394                | KeyEvent.FLAG_EDITOR_ACTION));
395        sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
396                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
397                KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
398                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
399                | KeyEvent.FLAG_EDITOR_ACTION));
400        return true;
401    }
402
403    /**
404     * The default implementation does nothing.
405     */
406    public boolean performContextMenuAction(int id) {
407        return false;
408    }
409
410    /**
411     * The default implementation does nothing.
412     */
413    public boolean performPrivateCommand(String action, Bundle data) {
414        return false;
415    }
416
417    /**
418     * The default implementation places the given text into the editable,
419     * replacing any existing composing text.  The new text is marked as
420     * in a composing state with the composing style.
421     */
422    public boolean setComposingText(CharSequence text, int newCursorPosition) {
423        if (DEBUG) Log.v(TAG, "setComposingText " + text);
424        replaceText(text, newCursorPosition, true);
425        return true;
426    }
427
428    public boolean setComposingRegion(int start, int end) {
429        final Editable content = getEditable();
430        if (content != null) {
431            beginBatchEdit();
432            removeComposingSpans(content);
433            int a = start;
434            int b = end;
435            if (a > b) {
436                int tmp = a;
437                a = b;
438                b = tmp;
439            }
440            // Clip the end points to be within the content bounds.
441            final int length = content.length();
442            if (a < 0) a = 0;
443            if (b < 0) b = 0;
444            if (a > length) a = length;
445            if (b > length) b = length;
446
447            ensureDefaultComposingSpans();
448            if (mDefaultComposingSpans != null) {
449                for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
450                    content.setSpan(mDefaultComposingSpans[i], a, b,
451                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
452                }
453            }
454
455            content.setSpan(COMPOSING, a, b,
456                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
457
458            endBatchEdit();
459            sendCurrentText();
460        }
461        return true;
462    }
463
464    /**
465     * The default implementation changes the selection position in the
466     * current editable text.
467     */
468    public boolean setSelection(int start, int end) {
469        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
470        final Editable content = getEditable();
471        if (content == null) return false;
472        int len = content.length();
473        if (start > len || end > len) {
474            // If the given selection is out of bounds, just ignore it.
475            // Most likely the text was changed out from under the IME,
476            // the the IME is going to have to update all of its state
477            // anyway.
478            return true;
479        }
480        if (start == end && MetaKeyKeyListener.getMetaState(content,
481                MetaKeyKeyListener.META_SELECTING) != 0) {
482            // If we are in selection mode, then we want to extend the
483            // selection instead of replacing it.
484            Selection.extendSelection(content, start);
485        } else {
486            Selection.setSelection(content, start, end);
487        }
488        return true;
489    }
490
491    /**
492     * Provides standard implementation for sending a key event to the window
493     * attached to the input connection's view.
494     */
495    public boolean sendKeyEvent(KeyEvent event) {
496        synchronized (mIMM.mH) {
497            Handler h = mTargetView != null ? mTargetView.getHandler() : null;
498            if (h == null) {
499                if (mIMM.mServedView != null) {
500                    h = mIMM.mServedView.getHandler();
501                }
502            }
503            if (h != null) {
504                h.sendMessage(h.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
505                        event));
506            }
507        }
508        return false;
509    }
510
511    /**
512     * Updates InputMethodManager with the current fullscreen mode.
513     */
514    public boolean reportFullscreenMode(boolean enabled) {
515        mIMM.setFullscreenMode(enabled);
516        return true;
517    }
518
519    private void sendCurrentText() {
520        if (!mDummyMode) {
521            return;
522        }
523
524        Editable content = getEditable();
525        if (content != null) {
526            final int N = content.length();
527            if (N == 0) {
528                return;
529            }
530            if (N == 1) {
531                // If it's 1 character, we have a chance of being
532                // able to generate normal key events...
533                if (mKeyCharacterMap == null) {
534                    mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
535                }
536                char[] chars = new char[1];
537                content.getChars(0, 1, chars, 0);
538                KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
539                if (events != null) {
540                    for (int i=0; i<events.length; i++) {
541                        if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
542                        sendKeyEvent(events[i]);
543                    }
544                    content.clear();
545                    return;
546                }
547            }
548
549            // Otherwise, revert to the special key event containing
550            // the actual characters.
551            KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
552                    content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
553            sendKeyEvent(event);
554            content.clear();
555        }
556    }
557
558    private void ensureDefaultComposingSpans() {
559        if (mDefaultComposingSpans == null) {
560            Context context;
561            if (mTargetView != null) {
562                context = mTargetView.getContext();
563            } else if (mIMM.mServedView != null) {
564                context = mIMM.mServedView.getContext();
565            } else {
566                context = null;
567            }
568            if (context != null) {
569                TypedArray ta = context.getTheme()
570                        .obtainStyledAttributes(new int[] {
571                                com.android.internal.R.attr.candidatesTextStyleSpans
572                        });
573                CharSequence style = ta.getText(0);
574                ta.recycle();
575                if (style != null && style instanceof Spanned) {
576                    mDefaultComposingSpans = ((Spanned)style).getSpans(
577                            0, style.length(), Object.class);
578                }
579            }
580        }
581    }
582
583    private void replaceText(CharSequence text, int newCursorPosition,
584            boolean composing) {
585        final Editable content = getEditable();
586        if (content == null) {
587            return;
588        }
589
590        beginBatchEdit();
591
592        // delete composing text set previously.
593        int a = getComposingSpanStart(content);
594        int b = getComposingSpanEnd(content);
595
596        if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
597
598        if (b < a) {
599            int tmp = a;
600            a = b;
601            b = tmp;
602        }
603
604        if (a != -1 && b != -1) {
605            removeComposingSpans(content);
606        } else {
607            a = Selection.getSelectionStart(content);
608            b = Selection.getSelectionEnd(content);
609            if (a < 0) a = 0;
610            if (b < 0) b = 0;
611            if (b < a) {
612                int tmp = a;
613                a = b;
614                b = tmp;
615            }
616        }
617
618        if (composing) {
619            Spannable sp = null;
620            if (!(text instanceof Spannable)) {
621                sp = new SpannableStringBuilder(text);
622                text = sp;
623                ensureDefaultComposingSpans();
624                if (mDefaultComposingSpans != null) {
625                    for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
626                        sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
627                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
628                    }
629                }
630            } else {
631                sp = (Spannable)text;
632            }
633            setComposingSpans(sp);
634        }
635
636        if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
637                + text + "\", composing=" + composing
638                + ", type=" + text.getClass().getCanonicalName());
639
640        if (DEBUG) {
641            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
642            lp.println("Current text:");
643            TextUtils.dumpSpans(content, lp, "  ");
644            lp.println("Composing text:");
645            TextUtils.dumpSpans(text, lp, "  ");
646        }
647
648        // Position the cursor appropriately, so that after replacing the
649        // desired range of text it will be located in the correct spot.
650        // This allows us to deal with filters performing edits on the text
651        // we are providing here.
652        if (newCursorPosition > 0) {
653            newCursorPosition += b - 1;
654        } else {
655            newCursorPosition += a;
656        }
657        if (newCursorPosition < 0) newCursorPosition = 0;
658        if (newCursorPosition > content.length())
659            newCursorPosition = content.length();
660        Selection.setSelection(content, newCursorPosition);
661
662        content.replace(a, b, text);
663
664        if (DEBUG) {
665            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
666            lp.println("Final text:");
667            TextUtils.dumpSpans(content, lp, "  ");
668        }
669
670        endBatchEdit();
671    }
672}
673