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.widget.TextView; 31 32 public 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