BaseInputConnection.java revision b2a3dd88a53cc8c6d19f6dc8ec4f3d6c4abd9b54
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.ViewRoot;
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 Handler mH;
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 dummyMode) {
64        mIMM = mgr;
65        mTargetView = null;
66        mH = null;
67        mDummyMode = dummyMode;
68    }
69
70    public BaseInputConnection(View targetView, boolean dummyMode) {
71        mIMM = (InputMethodManager)targetView.getContext().getSystemService(
72                Context.INPUT_METHOD_SERVICE);
73        mH = targetView.getHandler();
74        mTargetView = targetView;
75        mDummyMode = dummyMode;
76    }
77
78    public static final void removeComposingSpans(Spannable text) {
79        text.removeSpan(COMPOSING);
80        Object[] sps = text.getSpans(0, text.length(), Object.class);
81        if (sps != null) {
82            for (int i=sps.length-1; i>=0; i--) {
83                Object o = sps[i];
84                if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
85                    text.removeSpan(o);
86                }
87            }
88        }
89    }
90
91    public static void setComposingSpans(Spannable text) {
92        final Object[] sps = text.getSpans(0, text.length(), Object.class);
93        if (sps != null) {
94            for (int i=sps.length-1; i>=0; i--) {
95                final Object o = sps[i];
96                if (o == COMPOSING) {
97                    text.removeSpan(o);
98                    continue;
99                }
100                final int fl = text.getSpanFlags(o);
101                if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
102                        != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
103                    text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
104                            (fl&Spanned.SPAN_POINT_MARK_MASK)
105                                    | Spanned.SPAN_COMPOSING
106                                    | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
107                }
108            }
109        }
110
111        text.setSpan(COMPOSING, 0, text.length(),
112                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
113    }
114
115    public static int getComposingSpanStart(Spannable text) {
116        return text.getSpanStart(COMPOSING);
117    }
118
119    public static int getComposingSpanEnd(Spannable text) {
120        return text.getSpanEnd(COMPOSING);
121    }
122
123    /**
124     * Return the target of edit operations.  The default implementation
125     * returns its own fake editable that is just used for composing text;
126     * subclasses that are real text editors should override this and
127     * supply their own.
128     */
129    public Editable getEditable() {
130        if (mEditable == null) {
131            mEditable = Editable.Factory.getInstance().newEditable("");
132            Selection.setSelection(mEditable, 0);
133        }
134        return mEditable;
135    }
136
137    /**
138     * Default implementation does nothing.
139     */
140    public boolean beginBatchEdit() {
141        return false;
142    }
143
144    /**
145     * Default implementation does nothing.
146     */
147    public boolean endBatchEdit() {
148        return false;
149    }
150
151    /**
152     * Default implementation uses
153     * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
154     * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
155     */
156    public boolean clearMetaKeyStates(int states) {
157        final Editable content = getEditable();
158        if (content == null) return false;
159        MetaKeyKeyListener.clearMetaKeyState(content, states);
160        return true;
161    }
162
163    /**
164     * Default implementation does nothing.
165     */
166    public boolean commitCompletion(CompletionInfo text) {
167        return false;
168    }
169
170    /**
171     * Default implementation replaces any existing composing text with
172     * the given text.  In addition, only if dummy mode, a key event is
173     * sent for the new text and the current editable buffer cleared.
174     */
175    public boolean commitText(CharSequence text, int newCursorPosition) {
176        if (DEBUG) Log.v(TAG, "commitText " + text);
177        replaceText(text, newCursorPosition, false);
178        sendCurrentText();
179        return true;
180    }
181
182    /**
183     * The default implementation performs the deletion around the current
184     * selection position of the editable text.
185     */
186    public boolean deleteSurroundingText(int leftLength, int rightLength) {
187        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
188                + " / " + rightLength);
189        final Editable content = getEditable();
190        if (content == null) return false;
191
192        beginBatchEdit();
193
194        int a = Selection.getSelectionStart(content);
195        int b = Selection.getSelectionEnd(content);
196
197        if (a > b) {
198            int tmp = a;
199            a = b;
200            b = tmp;
201        }
202
203        // ignore the composing text.
204        int ca = getComposingSpanStart(content);
205        int cb = getComposingSpanEnd(content);
206        if (cb < ca) {
207            int tmp = ca;
208            ca = cb;
209            cb = tmp;
210        }
211        if (ca != -1 && cb != -1) {
212            if (ca < a) a = ca;
213            if (cb > b) b = cb;
214        }
215
216        int deleted = 0;
217
218        if (leftLength > 0) {
219            int start = a - leftLength;
220            if (start < 0) start = 0;
221            content.delete(start, a);
222            deleted = a - start;
223        }
224
225        if (rightLength > 0) {
226            b = b - deleted;
227
228            int end = b + rightLength;
229            if (end > content.length()) end = content.length();
230
231            content.delete(b, end);
232        }
233
234        endBatchEdit();
235
236        return true;
237    }
238
239    /**
240     * The default implementation removes the composing state from the
241     * current editable text.  In addition, only if dummy mode, a key event is
242     * sent for the new text and the current editable buffer cleared.
243     */
244    public boolean finishComposingText() {
245        if (DEBUG) Log.v(TAG, "finishComposingText");
246        final Editable content = getEditable();
247        if (content != null) {
248            beginBatchEdit();
249            removeComposingSpans(content);
250            endBatchEdit();
251            sendCurrentText();
252        }
253        return true;
254    }
255
256    /**
257     * The default implementation uses TextUtils.getCapsMode to get the
258     * cursor caps mode for the current selection position in the editable
259     * text, unless in dummy mode in which case 0 is always returned.
260     */
261    public int getCursorCapsMode(int reqModes) {
262        if (mDummyMode) return 0;
263
264        final Editable content = getEditable();
265        if (content == null) return 0;
266
267        int a = Selection.getSelectionStart(content);
268        int b = Selection.getSelectionEnd(content);
269
270        if (a > b) {
271            int tmp = a;
272            a = b;
273            b = tmp;
274        }
275
276        return TextUtils.getCapsMode(content, a, reqModes);
277    }
278
279    /**
280     * The default implementation always returns null.
281     */
282    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
283        return null;
284    }
285
286    /**
287     * The default implementation returns the given amount of text from the
288     * current cursor position in the buffer.
289     */
290    public CharSequence getTextBeforeCursor(int length, int flags) {
291        final Editable content = getEditable();
292        if (content == null) return null;
293
294        int a = Selection.getSelectionStart(content);
295        int b = Selection.getSelectionEnd(content);
296
297        if (a > b) {
298            int tmp = a;
299            a = b;
300            b = tmp;
301        }
302
303        if (length > a) {
304            length = a;
305        }
306
307        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
308            return content.subSequence(a - length, a);
309        }
310        return TextUtils.substring(content, a - length, a);
311    }
312
313    /**
314     * The default implementation returns the given amount of text from the
315     * current cursor position in the buffer.
316     */
317    public CharSequence getTextAfterCursor(int length, int flags) {
318        final Editable content = getEditable();
319        if (content == null) return null;
320
321        int a = Selection.getSelectionStart(content);
322        int b = Selection.getSelectionEnd(content);
323
324        if (a > b) {
325            int tmp = a;
326            a = b;
327            b = tmp;
328        }
329
330        if (b + length > content.length()) {
331            length = content.length() - b;
332        }
333
334
335        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
336            return content.subSequence(b, b + length);
337        }
338        return TextUtils.substring(content, b, b + length);
339    }
340
341    /**
342     * The default implementation does nothing.
343     */
344    public boolean performEditorAction(int actionCode) {
345        return false;
346    }
347
348    /**
349     * The default implementation does nothing.
350     */
351    public boolean performContextMenuAction(int id) {
352        return false;
353    }
354
355    /**
356     * The default implementation does nothing.
357     */
358    public boolean performPrivateCommand(String action, Bundle data) {
359        return false;
360    }
361
362    /**
363     * The default implementation places the given text into the editable,
364     * replacing any existing composing text.  The new text is marked as
365     * in a composing state with the composing style.
366     */
367    public boolean setComposingText(CharSequence text, int newCursorPosition) {
368        if (DEBUG) Log.v(TAG, "setComposingText " + text);
369        replaceText(text, newCursorPosition, true);
370        return true;
371    }
372
373    /**
374     * The default implementation changes the selection position in the
375     * current editable text.
376     */
377    public boolean setSelection(int start, int end) {
378        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
379        final Editable content = getEditable();
380        if (content == null) return false;
381        int len = content.length();
382        if (start > len || end > len) {
383            // If the given selection is out of bounds, just ignore it.
384            // Most likely the text was changed out from under the IME,
385            // the the IME is going to have to update all of its state
386            // anyway.
387            return true;
388        }
389        if (start == end && MetaKeyKeyListener.getMetaState(content,
390                MetaKeyKeyListener.META_SELECTING) != 0) {
391            // If we are in selection mode, then we want to extend the
392            // selection instead of replacing it.
393            Selection.extendSelection(content, start);
394        } else {
395            Selection.setSelection(content, start, end);
396        }
397        return true;
398    }
399
400    /**
401     * Provides standard implementation for sending a key event to the window
402     * attached to the input connection's view.
403     */
404    public boolean sendKeyEvent(KeyEvent event) {
405        synchronized (mIMM.mH) {
406            Handler h = mH;
407            if (h == null) {
408                if (mIMM.mServedView != null) {
409                    h = mIMM.mServedView.getHandler();
410                }
411            }
412            if (h != null) {
413                h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
414                        event));
415            }
416        }
417        return false;
418    }
419
420    /**
421     * Updates InputMethodManager with the current fullscreen mode.
422     */
423    public boolean reportFullscreenMode(boolean enabled) {
424        mIMM.setFullscreenMode(enabled);
425        return true;
426    }
427
428    private void sendCurrentText() {
429        if (!mDummyMode) {
430            return;
431        }
432
433        Editable content = getEditable();
434        if (content != null) {
435            final int N = content.length();
436            if (N == 0) {
437                return;
438            }
439            if (N == 1) {
440                // If it's 1 character, we have a chance of being
441                // able to generate normal key events...
442                if (mKeyCharacterMap == null) {
443                    mKeyCharacterMap = KeyCharacterMap.load(
444                            KeyCharacterMap.BUILT_IN_KEYBOARD);
445                }
446                char[] chars = new char[1];
447                content.getChars(0, 1, chars, 0);
448                KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
449                if (events != null) {
450                    for (int i=0; i<events.length; i++) {
451                        if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
452                        sendKeyEvent(events[i]);
453                    }
454                    content.clear();
455                    return;
456                }
457            }
458
459            // Otherwise, revert to the special key event containing
460            // the actual characters.
461            KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
462                    content.toString(), KeyCharacterMap.BUILT_IN_KEYBOARD, 0);
463            sendKeyEvent(event);
464            content.clear();
465        }
466    }
467
468    private void replaceText(CharSequence text, int newCursorPosition,
469            boolean composing) {
470        final Editable content = getEditable();
471        if (content == null) {
472            return;
473        }
474
475        beginBatchEdit();
476
477        // delete composing text set previously.
478        int a = getComposingSpanStart(content);
479        int b = getComposingSpanEnd(content);
480
481        if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
482
483        if (b < a) {
484            int tmp = a;
485            a = b;
486            b = tmp;
487        }
488
489        if (a != -1 && b != -1) {
490            removeComposingSpans(content);
491        } else {
492            a = Selection.getSelectionStart(content);
493            b = Selection.getSelectionEnd(content);
494            if (a >=0 && b>= 0 && a != b) {
495                if (b < a) {
496                    int tmp = a;
497                    a = b;
498                    b = tmp;
499                }
500            }
501        }
502
503        if (composing) {
504            Spannable sp = null;
505            if (!(text instanceof Spannable)) {
506                sp = new SpannableStringBuilder(text);
507                text = sp;
508                if (mDefaultComposingSpans == null) {
509                    Context context;
510                    if (mTargetView != null) {
511                        context = mTargetView.getContext();
512                    } else if (mIMM.mServedView != null) {
513                        context = mIMM.mServedView.getContext();
514                    } else {
515                        context = null;
516                    }
517                    if (context != null) {
518                        TypedArray ta = context.getTheme()
519                                .obtainStyledAttributes(new int[] {
520                                        com.android.internal.R.attr.candidatesTextStyleSpans
521                                });
522                        CharSequence style = ta.getText(0);
523                        ta.recycle();
524                        if (style != null && style instanceof Spanned) {
525                            mDefaultComposingSpans = ((Spanned)style).getSpans(
526                                    0, style.length(), Object.class);
527                        }
528                    }
529                }
530                if (mDefaultComposingSpans != null) {
531                    for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
532                        sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
533                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
534                    }
535                }
536            } else {
537                sp = (Spannable)text;
538            }
539            setComposingSpans(sp);
540        }
541
542        if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
543                + text + "\", composing=" + composing
544                + ", type=" + text.getClass().getCanonicalName());
545
546        if (DEBUG) {
547            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
548            lp.println("Current text:");
549            TextUtils.dumpSpans(content, lp, "  ");
550            lp.println("Composing text:");
551            TextUtils.dumpSpans(text, lp, "  ");
552        }
553
554        // Position the cursor appropriately, so that after replacing the
555        // desired range of text it will be located in the correct spot.
556        // This allows us to deal with filters performing edits on the text
557        // we are providing here.
558        if (newCursorPosition > 0) {
559            newCursorPosition += b - 1;
560        } else {
561            newCursorPosition += a;
562        }
563        if (newCursorPosition < 0) newCursorPosition = 0;
564        if (newCursorPosition > content.length())
565            newCursorPosition = content.length();
566        Selection.setSelection(content, newCursorPosition);
567
568        content.replace(a, b, text);
569
570        if (DEBUG) {
571            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
572            lp.println("Final text:");
573            TextUtils.dumpSpans(content, lp, "  ");
574        }
575
576        endBatchEdit();
577    }
578}
579