• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.internal.widget;
18 
19 import android.os.Bundle;
20 import android.text.Editable;
21 import android.text.Spanned;
22 import android.text.method.KeyListener;
23 import android.text.style.SuggestionSpan;
24 import android.util.Log;
25 import android.view.inputmethod.BaseInputConnection;
26 import android.view.inputmethod.CompletionInfo;
27 import android.view.inputmethod.CorrectionInfo;
28 import android.view.inputmethod.ExtractedText;
29 import android.view.inputmethod.ExtractedTextRequest;
30 import android.view.inputmethod.InputConnection;
31 import android.widget.TextView;
32 
33 public class EditableInputConnection extends BaseInputConnection {
34     private static final boolean DEBUG = false;
35     private static final String TAG = "EditableInputConnection";
36 
37     private final TextView mTextView;
38 
39     // Keeps track of nested begin/end batch edit to ensure this connection always has a
40     // balanced impact on its associated TextView.
41     // A negative value means that this connection has been finished by the InputMethodManager.
42     private int mBatchEditNesting;
43 
EditableInputConnection(TextView textview)44     public EditableInputConnection(TextView textview) {
45         super(textview, true);
46         mTextView = textview;
47     }
48 
49     @Override
getEditable()50     public Editable getEditable() {
51         TextView tv = mTextView;
52         if (tv != null) {
53             return tv.getEditableText();
54         }
55         return null;
56     }
57 
58     @Override
beginBatchEdit()59     public boolean beginBatchEdit() {
60         synchronized(this) {
61             if (mBatchEditNesting >= 0) {
62                 mTextView.beginBatchEdit();
63                 mBatchEditNesting++;
64                 return true;
65             }
66         }
67         return false;
68     }
69 
70     @Override
endBatchEdit()71     public boolean endBatchEdit() {
72         synchronized(this) {
73             if (mBatchEditNesting > 0) {
74                 // When the connection is reset by the InputMethodManager and reportFinish
75                 // is called, some endBatchEdit calls may still be asynchronously received from the
76                 // IME. Do not take these into account, thus ensuring that this IC's final
77                 // contribution to mTextView's nested batch edit count is zero.
78                 mTextView.endBatchEdit();
79                 mBatchEditNesting--;
80                 return true;
81             }
82         }
83         return false;
84     }
85 
86     @Override
reportFinish()87     protected void reportFinish() {
88         super.reportFinish();
89 
90         synchronized(this) {
91             while (mBatchEditNesting > 0) {
92                 endBatchEdit();
93             }
94             // Will prevent any further calls to begin or endBatchEdit
95             mBatchEditNesting = -1;
96         }
97     }
98 
99     @Override
clearMetaKeyStates(int states)100     public boolean clearMetaKeyStates(int states) {
101         final Editable content = getEditable();
102         if (content == null) return false;
103         KeyListener kl = mTextView.getKeyListener();
104         if (kl != null) {
105             try {
106                 kl.clearMetaKeyState(mTextView, content, states);
107             } catch (AbstractMethodError e) {
108                 // This is an old listener that doesn't implement the
109                 // new method.
110             }
111         }
112         return true;
113     }
114 
115     @Override
commitCompletion(CompletionInfo text)116     public boolean commitCompletion(CompletionInfo text) {
117         if (DEBUG) Log.v(TAG, "commitCompletion " + text);
118         mTextView.beginBatchEdit();
119         mTextView.onCommitCompletion(text);
120         mTextView.endBatchEdit();
121         return true;
122     }
123 
124     /**
125      * Calls the {@link TextView#onCommitCorrection} method of the associated TextView.
126      */
127     @Override
commitCorrection(CorrectionInfo correctionInfo)128     public boolean commitCorrection(CorrectionInfo correctionInfo) {
129         if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
130         mTextView.beginBatchEdit();
131         mTextView.onCommitCorrection(correctionInfo);
132         mTextView.endBatchEdit();
133         return true;
134     }
135 
136     @Override
performEditorAction(int actionCode)137     public boolean performEditorAction(int actionCode) {
138         if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
139         mTextView.onEditorAction(actionCode);
140         return true;
141     }
142 
143     @Override
performContextMenuAction(int id)144     public boolean performContextMenuAction(int id) {
145         if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
146         mTextView.beginBatchEdit();
147         mTextView.onTextContextMenuItem(id);
148         mTextView.endBatchEdit();
149         return true;
150     }
151 
152     @Override
getExtractedText(ExtractedTextRequest request, int flags)153     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
154         if (mTextView != null) {
155             ExtractedText et = new ExtractedText();
156             if (mTextView.extractText(request, et)) {
157                 if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
158                     mTextView.setExtracting(request);
159                 }
160                 return et;
161             }
162         }
163         return null;
164     }
165 
166     @Override
performPrivateCommand(String action, Bundle data)167     public boolean performPrivateCommand(String action, Bundle data) {
168         mTextView.onPrivateIMECommand(action, data);
169         return true;
170     }
171 
172     @Override
commitText(CharSequence text, int newCursorPosition)173     public boolean commitText(CharSequence text, int newCursorPosition) {
174         if (mTextView == null) {
175             return super.commitText(text, newCursorPosition);
176         }
177         if (text instanceof Spanned) {
178             Spanned spanned = ((Spanned) text);
179             SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
180             mIMM.registerSuggestionSpansForNotification(spans);
181         }
182 
183         mTextView.resetErrorChangedFlag();
184         boolean success = super.commitText(text, newCursorPosition);
185         mTextView.hideErrorIfUnchanged();
186 
187         return success;
188     }
189 
190     @Override
requestCursorUpdates(int cursorUpdateMode)191     public boolean requestCursorUpdates(int cursorUpdateMode) {
192         if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
193 
194         // It is possible that any other bit is used as a valid flag in a future release.
195         // We should reject the entire request in such a case.
196         final int KNOWN_FLAGS_MASK = InputConnection.CURSOR_UPDATE_IMMEDIATE |
197                 InputConnection.CURSOR_UPDATE_MONITOR;
198         final int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK;
199         if (unknownFlags != 0) {
200             if (DEBUG) {
201                 Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." +
202                         " cursorUpdateMode=" + cursorUpdateMode +
203                         " unknownFlags=" + unknownFlags);
204             }
205             return false;
206         }
207 
208         if (mIMM == null) {
209             // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
210             // TODO: Return some notification code rather than false to indicate method that
211             // CursorAnchorInfo is temporarily unavailable.
212             return false;
213         }
214         mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode);
215         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
216             if (mTextView == null) {
217                 // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored.
218                 // TODO: Return some notification code for the input method that indicates
219                 // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored.
220             } else if (mTextView.isInLayout()) {
221                 // In this case, the view hierarchy is currently undergoing a layout pass.
222                 // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
223                 // pass is finished.
224             } else {
225                 // This will schedule a layout pass of the view tree, and the layout event
226                 // eventually triggers IMM#updateCursorAnchorInfo.
227                 mTextView.requestLayout();
228             }
229         }
230         return true;
231     }
232 }
233