BaseInputConnection.java revision ff328ae7438a9c5c2fe49c286833a30e25015e63
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        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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
420    public boolean performContextMenuAction(int id) {
421        return false;
422    }
423
424    /**
425     * The default implementation does nothing.
426     */
427    public boolean performPrivateCommand(String action, Bundle data) {
428        return false;
429    }
430
431    /**
432     * The default implementation is responsible for handling
433     * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}. In fact, for derived classes, calling
434     * {@code super.requestCursorAnchorInfo(request)} is the only way to handle
435     * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}.
436     */
437    public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
438        // This implementation supports TYPE_CURSOR_RECT only.
439        if (request == null ||
440                request.getRequestType() != CursorAnchorInfoRequest.TYPE_CURSOR_RECT) {
441            return CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
442        }
443        if (mIMM == null) {
444            // In this case, TYPE_CURSOR_RECT is not handled.
445            // TODO: Return some notification code for the input method that indicates
446            // Cursor rect information is temporarily unavailable.
447            return CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
448        }
449        mIMM.setCursorRectMonitorMode(request.getRequestFlags());
450        return CursorAnchorInfoRequest.RESULT_SCHEDULED;
451    }
452
453    /**
454     * The default implementation places the given text into the editable,
455     * replacing any existing composing text.  The new text is marked as
456     * in a composing state with the composing style.
457     */
458    public boolean setComposingText(CharSequence text, int newCursorPosition) {
459        if (DEBUG) Log.v(TAG, "setComposingText " + text);
460        replaceText(text, newCursorPosition, true);
461        mIMM.notifyUserAction();
462        return true;
463    }
464
465    public boolean setComposingRegion(int start, int end) {
466        final Editable content = getEditable();
467        if (content != null) {
468            beginBatchEdit();
469            removeComposingSpans(content);
470            int a = start;
471            int b = end;
472            if (a > b) {
473                int tmp = a;
474                a = b;
475                b = tmp;
476            }
477            // Clip the end points to be within the content bounds.
478            final int length = content.length();
479            if (a < 0) a = 0;
480            if (b < 0) b = 0;
481            if (a > length) a = length;
482            if (b > length) b = length;
483
484            ensureDefaultComposingSpans();
485            if (mDefaultComposingSpans != null) {
486                for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
487                    content.setSpan(mDefaultComposingSpans[i], a, b,
488                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
489                }
490            }
491
492            content.setSpan(COMPOSING, a, b,
493                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
494
495            // Note: sendCurrentText does nothing unless mDummyMode is set
496            sendCurrentText();
497            endBatchEdit();
498        }
499        return true;
500    }
501
502    /**
503     * The default implementation changes the selection position in the
504     * current editable text.
505     */
506    public boolean setSelection(int start, int end) {
507        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
508        final Editable content = getEditable();
509        if (content == null) return false;
510        int len = content.length();
511        if (start > len || end > len || start < 0 || end < 0) {
512            // If the given selection is out of bounds, just ignore it.
513            // Most likely the text was changed out from under the IME,
514            // and the IME is going to have to update all of its state
515            // anyway.
516            return true;
517        }
518        if (start == end && MetaKeyKeyListener.getMetaState(content,
519                MetaKeyKeyListener.META_SELECTING) != 0) {
520            // If we are in selection mode, then we want to extend the
521            // selection instead of replacing it.
522            Selection.extendSelection(content, start);
523        } else {
524            Selection.setSelection(content, start, end);
525        }
526        return true;
527    }
528
529    /**
530     * Provides standard implementation for sending a key event to the window
531     * attached to the input connection's view.
532     */
533    public boolean sendKeyEvent(KeyEvent event) {
534        synchronized (mIMM.mH) {
535            ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
536            if (viewRootImpl == null) {
537                if (mIMM.mServedView != null) {
538                    viewRootImpl = mIMM.mServedView.getViewRootImpl();
539                }
540            }
541            if (viewRootImpl != null) {
542                viewRootImpl.dispatchKeyFromIme(event);
543            }
544        }
545        mIMM.notifyUserAction();
546        return false;
547    }
548
549    /**
550     * Updates InputMethodManager with the current fullscreen mode.
551     */
552    public boolean reportFullscreenMode(boolean enabled) {
553        mIMM.setFullscreenMode(enabled);
554        return true;
555    }
556
557    private void sendCurrentText() {
558        if (!mDummyMode) {
559            return;
560        }
561
562        Editable content = getEditable();
563        if (content != null) {
564            final int N = content.length();
565            if (N == 0) {
566                return;
567            }
568            if (N == 1) {
569                // If it's 1 character, we have a chance of being
570                // able to generate normal key events...
571                if (mKeyCharacterMap == null) {
572                    mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
573                }
574                char[] chars = new char[1];
575                content.getChars(0, 1, chars, 0);
576                KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
577                if (events != null) {
578                    for (int i=0; i<events.length; i++) {
579                        if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
580                        sendKeyEvent(events[i]);
581                    }
582                    content.clear();
583                    return;
584                }
585            }
586
587            // Otherwise, revert to the special key event containing
588            // the actual characters.
589            KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
590                    content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
591            sendKeyEvent(event);
592            content.clear();
593        }
594    }
595
596    private void ensureDefaultComposingSpans() {
597        if (mDefaultComposingSpans == null) {
598            Context context;
599            if (mTargetView != null) {
600                context = mTargetView.getContext();
601            } else if (mIMM.mServedView != null) {
602                context = mIMM.mServedView.getContext();
603            } else {
604                context = null;
605            }
606            if (context != null) {
607                TypedArray ta = context.getTheme()
608                        .obtainStyledAttributes(new int[] {
609                                com.android.internal.R.attr.candidatesTextStyleSpans
610                        });
611                CharSequence style = ta.getText(0);
612                ta.recycle();
613                if (style != null && style instanceof Spanned) {
614                    mDefaultComposingSpans = ((Spanned)style).getSpans(
615                            0, style.length(), Object.class);
616                }
617            }
618        }
619    }
620
621    private void replaceText(CharSequence text, int newCursorPosition,
622            boolean composing) {
623        final Editable content = getEditable();
624        if (content == null) {
625            return;
626        }
627
628        beginBatchEdit();
629
630        // delete composing text set previously.
631        int a = getComposingSpanStart(content);
632        int b = getComposingSpanEnd(content);
633
634        if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
635
636        if (b < a) {
637            int tmp = a;
638            a = b;
639            b = tmp;
640        }
641
642        if (a != -1 && b != -1) {
643            removeComposingSpans(content);
644        } else {
645            a = Selection.getSelectionStart(content);
646            b = Selection.getSelectionEnd(content);
647            if (a < 0) a = 0;
648            if (b < 0) b = 0;
649            if (b < a) {
650                int tmp = a;
651                a = b;
652                b = tmp;
653            }
654        }
655
656        if (composing) {
657            Spannable sp = null;
658            if (!(text instanceof Spannable)) {
659                sp = new SpannableStringBuilder(text);
660                text = sp;
661                ensureDefaultComposingSpans();
662                if (mDefaultComposingSpans != null) {
663                    for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
664                        sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
665                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
666                    }
667                }
668            } else {
669                sp = (Spannable)text;
670            }
671            setComposingSpans(sp);
672        }
673
674        if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
675                + text + "\", composing=" + composing
676                + ", type=" + text.getClass().getCanonicalName());
677
678        if (DEBUG) {
679            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
680            lp.println("Current text:");
681            TextUtils.dumpSpans(content, lp, "  ");
682            lp.println("Composing text:");
683            TextUtils.dumpSpans(text, lp, "  ");
684        }
685
686        // Position the cursor appropriately, so that after replacing the
687        // desired range of text it will be located in the correct spot.
688        // This allows us to deal with filters performing edits on the text
689        // we are providing here.
690        if (newCursorPosition > 0) {
691            newCursorPosition += b - 1;
692        } else {
693            newCursorPosition += a;
694        }
695        if (newCursorPosition < 0) newCursorPosition = 0;
696        if (newCursorPosition > content.length())
697            newCursorPosition = content.length();
698        Selection.setSelection(content, newCursorPosition);
699
700        content.replace(a, b, text);
701
702        if (DEBUG) {
703            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
704            lp.println("Final text:");
705            TextUtils.dumpSpans(content, lp, "  ");
706        }
707
708        endBatchEdit();
709    }
710}
711