• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 
17 package com.android.internal.inputmethod;
18 
19 import static android.view.inputmethod.InputConnectionProto.CURSOR_CAPS_MODE;
20 import static android.view.inputmethod.InputConnectionProto.EDITABLE_TEXT;
21 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT;
22 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_END;
23 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START;
24 
25 import android.os.Bundle;
26 import android.text.Editable;
27 import android.text.Selection;
28 import android.text.method.KeyListener;
29 import android.util.Log;
30 import android.util.proto.ProtoOutputStream;
31 import android.view.inputmethod.BaseInputConnection;
32 import android.view.inputmethod.CompletionInfo;
33 import android.view.inputmethod.CorrectionInfo;
34 import android.view.inputmethod.DumpableInputConnection;
35 import android.view.inputmethod.ExtractedText;
36 import android.view.inputmethod.ExtractedTextRequest;
37 import android.view.inputmethod.InputConnection;
38 import android.widget.TextView;
39 
40 /**
41  * Base class for an editable InputConnection instance. This is created by {@link TextView} or
42  * {@link android.widget.EditText}.
43  */
44 public final class EditableInputConnection extends BaseInputConnection
45         implements DumpableInputConnection {
46     private static final boolean DEBUG = false;
47     private static final String TAG = "EditableInputConnection";
48 
49     private final TextView mTextView;
50 
51     // Keeps track of nested begin/end batch edit to ensure this connection always has a
52     // balanced impact on its associated TextView.
53     // A negative value means that this connection has been finished by the InputMethodManager.
54     private int mBatchEditNesting;
55 
EditableInputConnection(TextView textview)56     public EditableInputConnection(TextView textview) {
57         super(textview, true);
58         mTextView = textview;
59     }
60 
61     @Override
getEditable()62     public Editable getEditable() {
63         TextView tv = mTextView;
64         if (tv != null) {
65             return tv.getEditableText();
66         }
67         return null;
68     }
69 
70     @Override
beginBatchEdit()71     public boolean beginBatchEdit() {
72         synchronized (this) {
73             if (mBatchEditNesting >= 0) {
74                 mTextView.beginBatchEdit();
75                 mBatchEditNesting++;
76                 return true;
77             }
78         }
79         return false;
80     }
81 
82     @Override
endBatchEdit()83     public boolean endBatchEdit() {
84         synchronized (this) {
85             if (mBatchEditNesting > 0) {
86                 // When the connection is reset by the InputMethodManager and reportFinish
87                 // is called, some endBatchEdit calls may still be asynchronously received from the
88                 // IME. Do not take these into account, thus ensuring that this IC's final
89                 // contribution to mTextView's nested batch edit count is zero.
90                 mTextView.endBatchEdit();
91                 mBatchEditNesting--;
92                 return mBatchEditNesting > 0;
93             }
94         }
95         return false;
96     }
97 
98     @Override
endComposingRegionEditInternal()99     public void endComposingRegionEditInternal() {
100         // The ContentCapture service is interested in Composing-state changes.
101         mTextView.notifyContentCaptureTextChanged();
102     }
103 
104     @Override
closeConnection()105     public void closeConnection() {
106         super.closeConnection();
107         synchronized (this) {
108             while (mBatchEditNesting > 0) {
109                 endBatchEdit();
110             }
111             // Will prevent any further calls to begin or endBatchEdit
112             mBatchEditNesting = -1;
113         }
114     }
115 
116     @Override
clearMetaKeyStates(int states)117     public boolean clearMetaKeyStates(int states) {
118         final Editable content = getEditable();
119         if (content == null) return false;
120         KeyListener kl = mTextView.getKeyListener();
121         if (kl != null) {
122             try {
123                 kl.clearMetaKeyState(mTextView, content, states);
124             } catch (AbstractMethodError e) {
125                 // This is an old listener that doesn't implement the
126                 // new method.
127             }
128         }
129         return true;
130     }
131 
132     @Override
commitCompletion(CompletionInfo text)133     public boolean commitCompletion(CompletionInfo text) {
134         if (DEBUG) Log.v(TAG, "commitCompletion " + text);
135         mTextView.beginBatchEdit();
136         mTextView.onCommitCompletion(text);
137         mTextView.endBatchEdit();
138         return true;
139     }
140 
141     /**
142      * Calls the {@link TextView#onCommitCorrection} method of the associated TextView.
143      */
144     @Override
commitCorrection(CorrectionInfo correctionInfo)145     public boolean commitCorrection(CorrectionInfo correctionInfo) {
146         if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
147         mTextView.beginBatchEdit();
148         mTextView.onCommitCorrection(correctionInfo);
149         mTextView.endBatchEdit();
150         return true;
151     }
152 
153     @Override
performEditorAction(int actionCode)154     public boolean performEditorAction(int actionCode) {
155         if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
156         mTextView.onEditorAction(actionCode);
157         return true;
158     }
159 
160     @Override
performContextMenuAction(int id)161     public boolean performContextMenuAction(int id) {
162         if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
163         mTextView.beginBatchEdit();
164         mTextView.onTextContextMenuItem(id);
165         mTextView.endBatchEdit();
166         return true;
167     }
168 
169     @Override
getExtractedText(ExtractedTextRequest request, int flags)170     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
171         if (mTextView != null) {
172             ExtractedText et = new ExtractedText();
173             if (mTextView.extractText(request, et)) {
174                 if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0) {
175                     mTextView.setExtracting(request);
176                 }
177                 return et;
178             }
179         }
180         return null;
181     }
182 
183     @Override
performSpellCheck()184     public boolean performSpellCheck() {
185         mTextView.onPerformSpellCheck();
186         return true;
187     }
188 
189     @Override
performPrivateCommand(String action, Bundle data)190     public boolean performPrivateCommand(String action, Bundle data) {
191         mTextView.onPrivateIMECommand(action, data);
192         return true;
193     }
194 
195     @Override
commitText(CharSequence text, int newCursorPosition)196     public boolean commitText(CharSequence text, int newCursorPosition) {
197         if (mTextView == null) {
198             return super.commitText(text, newCursorPosition);
199         }
200         mTextView.resetErrorChangedFlag();
201         boolean success = super.commitText(text, newCursorPosition);
202         mTextView.hideErrorIfUnchanged();
203 
204         return success;
205     }
206 
207     @Override
requestCursorUpdates( @ursorUpdateMode int cursorUpdateMode, @CursorUpdateFilter int cursorUpdateFilter)208     public boolean requestCursorUpdates(
209             @CursorUpdateMode int cursorUpdateMode, @CursorUpdateFilter int cursorUpdateFilter) {
210         // TODO(b/210039666): use separate attrs for updateMode and updateFilter.
211         return requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter);
212     }
213 
214     @Override
requestCursorUpdates(int cursorUpdateMode)215     public boolean requestCursorUpdates(int cursorUpdateMode) {
216         if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
217 
218         // It is possible that any other bit is used as a valid flag in a future release.
219         // We should reject the entire request in such a case.
220         final int knownFlagMask = InputConnection.CURSOR_UPDATE_IMMEDIATE
221                 | InputConnection.CURSOR_UPDATE_MONITOR
222                 | InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
223                 | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
224                 | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS;
225         final int unknownFlags = cursorUpdateMode & ~knownFlagMask;
226         if (unknownFlags != 0) {
227             if (DEBUG) {
228                 Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags. "
229                         + "cursorUpdateMode=" + cursorUpdateMode + " unknownFlags=" + unknownFlags);
230             }
231             return false;
232         }
233 
234         if (mIMM == null) {
235             // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
236             // TODO: Return some notification code rather than false to indicate method that
237             // CursorAnchorInfo is temporarily unavailable.
238             return false;
239         }
240         mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode);
241         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
242             if (mTextView == null) {
243                 // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored.
244                 // TODO: Return some notification code for the input method that indicates
245                 // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored.
246             } else if (mTextView.isInLayout()) {
247                 // In this case, the view hierarchy is currently undergoing a layout pass.
248                 // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
249                 // pass is finished.
250             } else {
251                 // This will schedule a layout pass of the view tree, and the layout event
252                 // eventually triggers IMM#updateCursorAnchorInfo.
253                 mTextView.requestLayout();
254             }
255         }
256         return true;
257     }
258 
259     @Override
setImeConsumesInput(boolean imeConsumesInput)260     public boolean setImeConsumesInput(boolean imeConsumesInput) {
261         if (mTextView == null) {
262             return super.setImeConsumesInput(imeConsumesInput);
263         }
264         mTextView.setImeConsumesInput(imeConsumesInput);
265         return true;
266     }
267 
268     @Override
dumpDebug(ProtoOutputStream proto, long fieldId)269     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
270         final long token = proto.start(fieldId);
271         CharSequence editableText = mTextView.getText();
272         CharSequence selectedText = getSelectedText(0 /* flags */);
273         if (InputConnectionProtoDumper.DUMP_TEXT) {
274             if (editableText != null) {
275                 proto.write(EDITABLE_TEXT, editableText.toString());
276             }
277             if (selectedText != null) {
278                 proto.write(SELECTED_TEXT, selectedText.toString());
279             }
280         }
281         final Editable content = getEditable();
282         if (content != null) {
283             int start = Selection.getSelectionStart(content);
284             int end = Selection.getSelectionEnd(content);
285             proto.write(SELECTED_TEXT_START, start);
286             proto.write(SELECTED_TEXT_END, end);
287         }
288         proto.write(CURSOR_CAPS_MODE, getCursorCapsMode(0));
289         proto.end(token);
290     }
291 }
292