1/*
2 * Copyright (C) 2007-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 com.android.internal.widget;
18
19import android.os.Bundle;
20import android.text.Editable;
21import android.text.Spanned;
22import android.text.method.KeyListener;
23import android.text.style.SuggestionSpan;
24import android.util.Log;
25import android.view.inputmethod.BaseInputConnection;
26import android.view.inputmethod.CompletionInfo;
27import android.view.inputmethod.CorrectionInfo;
28import android.view.inputmethod.ExtractedText;
29import android.view.inputmethod.ExtractedTextRequest;
30import android.widget.TextView;
31
32public class EditableInputConnection extends BaseInputConnection {
33    private static final boolean DEBUG = false;
34    private static final String TAG = "EditableInputConnection";
35
36    private final TextView mTextView;
37
38    // Keeps track of nested begin/end batch edit to ensure this connection always has a
39    // balanced impact on its associated TextView.
40    // A negative value means that this connection has been finished by the InputMethodManager.
41    private int mBatchEditNesting;
42
43    public EditableInputConnection(TextView textview) {
44        super(textview, true);
45        mTextView = textview;
46    }
47
48    @Override
49    public Editable getEditable() {
50        TextView tv = mTextView;
51        if (tv != null) {
52            return tv.getEditableText();
53        }
54        return null;
55    }
56
57    @Override
58    public boolean beginBatchEdit() {
59        synchronized(this) {
60            if (mBatchEditNesting >= 0) {
61                mTextView.beginBatchEdit();
62                mBatchEditNesting++;
63                return true;
64            }
65        }
66        return false;
67    }
68
69    @Override
70    public boolean endBatchEdit() {
71        synchronized(this) {
72            if (mBatchEditNesting > 0) {
73                // When the connection is reset by the InputMethodManager and reportFinish
74                // is called, some endBatchEdit calls may still be asynchronously received from the
75                // IME. Do not take these into account, thus ensuring that this IC's final
76                // contribution to mTextView's nested batch edit count is zero.
77                mTextView.endBatchEdit();
78                mBatchEditNesting--;
79                return true;
80            }
81        }
82        return false;
83    }
84
85    @Override
86    protected void reportFinish() {
87        super.reportFinish();
88
89        synchronized(this) {
90            while (mBatchEditNesting > 0) {
91                endBatchEdit();
92            }
93            // Will prevent any further calls to begin or endBatchEdit
94            mBatchEditNesting = -1;
95        }
96    }
97
98    @Override
99    public boolean clearMetaKeyStates(int states) {
100        final Editable content = getEditable();
101        if (content == null) return false;
102        KeyListener kl = mTextView.getKeyListener();
103        if (kl != null) {
104            try {
105                kl.clearMetaKeyState(mTextView, content, states);
106            } catch (AbstractMethodError e) {
107                // This is an old listener that doesn't implement the
108                // new method.
109            }
110        }
111        return true;
112    }
113
114    @Override
115    public boolean commitCompletion(CompletionInfo text) {
116        if (DEBUG) Log.v(TAG, "commitCompletion " + text);
117        mTextView.beginBatchEdit();
118        mTextView.onCommitCompletion(text);
119        mTextView.endBatchEdit();
120        return true;
121    }
122
123    /**
124     * Calls the {@link TextView#onCommitCorrection} method of the associated TextView.
125     */
126    @Override
127    public boolean commitCorrection(CorrectionInfo correctionInfo) {
128        if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
129        mTextView.beginBatchEdit();
130        mTextView.onCommitCorrection(correctionInfo);
131        mTextView.endBatchEdit();
132        return true;
133    }
134
135    @Override
136    public boolean performEditorAction(int actionCode) {
137        if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
138        mTextView.onEditorAction(actionCode);
139        return true;
140    }
141
142    @Override
143    public boolean performContextMenuAction(int id) {
144        if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
145        mTextView.beginBatchEdit();
146        mTextView.onTextContextMenuItem(id);
147        mTextView.endBatchEdit();
148        return true;
149    }
150
151    @Override
152    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
153        if (mTextView != null) {
154            ExtractedText et = new ExtractedText();
155            if (mTextView.extractText(request, et)) {
156                if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
157                    mTextView.setExtracting(request);
158                }
159                return et;
160            }
161        }
162        return null;
163    }
164
165    @Override
166    public boolean performPrivateCommand(String action, Bundle data) {
167        mTextView.onPrivateIMECommand(action, data);
168        return true;
169    }
170
171    @Override
172    public boolean commitText(CharSequence text, int newCursorPosition) {
173        if (mTextView == null) {
174            return super.commitText(text, newCursorPosition);
175        }
176        if (text instanceof Spanned) {
177            Spanned spanned = ((Spanned) text);
178            SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
179            mIMM.registerSuggestionSpansForNotification(spans);
180        }
181
182        mTextView.resetErrorChangedFlag();
183        boolean success = super.commitText(text, newCursorPosition);
184        mTextView.hideErrorIfUnchanged();
185
186        return success;
187    }
188}
189