1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.inputmethodservice;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.view.inputmethod.ExtractedText;
22import android.view.inputmethod.InputMethodManager;
23import android.widget.EditText;
24
25/***
26 * Specialization of {@link EditText} for showing and interacting with the
27 * extracted text in a full-screen input method.
28 */
29public class ExtractEditText extends EditText {
30    private InputMethodService mIME;
31    private int mSettingExtractedText;
32
33    public ExtractEditText(Context context) {
34        super(context, null);
35    }
36
37    public ExtractEditText(Context context, AttributeSet attrs) {
38        super(context, attrs, com.android.internal.R.attr.editTextStyle);
39    }
40
41    public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr) {
42        this(context, attrs, defStyleAttr, 0);
43    }
44
45    public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
46        super(context, attrs, defStyleAttr, defStyleRes);
47    }
48
49    void setIME(InputMethodService ime) {
50        mIME = ime;
51    }
52
53    /**
54     * Start making changes that will not be reported to the client.  That
55     * is, {@link #onSelectionChanged(int, int)} will not result in sending
56     * the new selection to the client
57     */
58    public void startInternalChanges() {
59        mSettingExtractedText += 1;
60    }
61
62    /**
63     * Finish making changes that will not be reported to the client.  That
64     * is, {@link #onSelectionChanged(int, int)} will not result in sending
65     * the new selection to the client
66     */
67    public void finishInternalChanges() {
68        mSettingExtractedText -= 1;
69    }
70
71    /**
72     * Implement just to keep track of when we are setting text from the
73     * client (vs. seeing changes in ourself from the user).
74     */
75    @Override public void setExtractedText(ExtractedText text) {
76        try {
77            mSettingExtractedText++;
78            super.setExtractedText(text);
79        } finally {
80            mSettingExtractedText--;
81        }
82    }
83
84    /**
85     * Report to the underlying text editor about selection changes.
86     */
87    @Override protected void onSelectionChanged(int selStart, int selEnd) {
88        if (mSettingExtractedText == 0 && mIME != null && selStart >= 0 && selEnd >= 0) {
89            mIME.onExtractedSelectionChanged(selStart, selEnd);
90        }
91    }
92
93    /**
94     * Redirect clicks to the IME for handling there.  First allows any
95     * on click handler to run, though.
96     */
97    @Override public boolean performClick() {
98        if (!super.performClick() && mIME != null) {
99            mIME.onExtractedTextClicked();
100            return true;
101        }
102        return false;
103    }
104
105    @Override public boolean onTextContextMenuItem(int id) {
106        // Select all and Replace text shouldn't be handled by the original edit text, but by the
107        // extracted one.
108        if (id == android.R.id.selectAll || id == android.R.id.replaceText) {
109            return super.onTextContextMenuItem(id);
110        }
111        if (mIME != null && mIME.onExtractTextContextMenuItem(id)) {
112            // Mode was started on Extracted, needs to be stopped here.
113            // Cut will change the text, which stops selection mode.
114            if (id == android.R.id.copy || id == android.R.id.paste) stopTextActionMode();
115            return true;
116        }
117        return super.onTextContextMenuItem(id);
118    }
119
120    /**
121     * We are always considered to be an input method target.
122     */
123    @Override
124    public boolean isInputMethodTarget() {
125        return true;
126    }
127
128    /**
129     * Return true if the edit text is currently showing a scroll bar.
130     */
131    public boolean hasVerticalScrollBar() {
132        return computeVerticalScrollRange() > computeVerticalScrollExtent();
133    }
134
135    /**
136     * Pretend like the window this view is in always has focus, so its
137     * highlight and cursor will be displayed.
138     */
139    @Override public boolean hasWindowFocus() {
140        return this.isEnabled();
141    }
142
143    /**
144     * Pretend like this view always has focus, so its
145     * highlight and cursor will be displayed.
146     */
147    @Override public boolean isFocused() {
148        return this.isEnabled();
149    }
150
151    /**
152     * Pretend like this view always has focus, so its
153     * highlight and cursor will be displayed.
154     */
155    @Override public boolean hasFocus() {
156        return this.isEnabled();
157    }
158
159    /**
160     * @hide
161     */
162    @Override protected void viewClicked(InputMethodManager imm) {
163        // As an instance of this class is supposed to be owned by IMS,
164        // and it has a reference to the IMS (the current IME),
165        // we just need to call back its onViewClicked() here.
166        // It should be good to avoid unnecessary IPCs by doing this as well.
167        if (mIME != null) {
168            mIME.onViewClicked(false);
169        }
170    }
171
172    /**
173     * @hide
174     */
175    @Override
176    public boolean isInExtractedMode() {
177        return true;
178    }
179
180    /**
181     * {@inheritDoc}
182     * @hide
183     */
184    @Override
185    protected void deleteText_internal(int start, int end) {
186        // Do not call the super method.
187        // This will change the source TextView instead, which will update the ExtractTextView.
188        mIME.onExtractedDeleteText(start, end);
189    }
190
191    /**
192     * {@inheritDoc}
193     * @hide
194     */
195    @Override
196    protected void replaceText_internal(int start, int end, CharSequence text) {
197        // Do not call the super method.
198        // This will change the source TextView instead, which will update the ExtractTextView.
199        mIME.onExtractedReplaceText(start, end, text);
200    }
201
202    /**
203     * {@inheritDoc}
204     * @hide
205     */
206    @Override
207    protected void setSpan_internal(Object span, int start, int end, int flags) {
208        // Do not call the super method.
209        // This will change the source TextView instead, which will update the ExtractTextView.
210        mIME.onExtractedSetSpan(span, start, end, flags);
211    }
212
213    /**
214     * {@inheritDoc}
215     * @hide
216     */
217    @Override
218    protected void setCursorPosition_internal(int start, int end) {
219        // Do not call the super method.
220        // This will change the source TextView instead, which will update the ExtractTextView.
221        mIME.onExtractedSelectionChanged(start, end);
222    }
223}
224