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