Skip to content

Commit c62589c

Browse files
author
Gilles Debunne
committed
Editor uses a SpanWatcher to track EasyEditSpans
Will also fix Bug 6344997 The previous TextWatcher mechanism was inneficient. It require an expensive getSpans() call to retrieve all the spans and then search for the one we're interested in in case it has been changed. The SpanWatcher is faster, it will broadcast the add/changed/removed events we're interested in. Now that we can rely on SpanWatcher, use it to directly track addition and removals of EasyEditSpans. No unit test for this feature which require an integration with the voice IME. Easy to test manually though. Change-Id: Idabcacc48c479bf9868d5204c0b0ca709207ede2
1 parent e267f5f commit c62589c

File tree

4 files changed

+81
-132
lines changed

4 files changed

+81
-132
lines changed

core/java/android/view/inputmethod/ExtractedText.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class ExtractedText implements Parcelable {
4545
/**
4646
* If the content is a report of a partial text change, this is the offset
4747
* where the change ends. Note that the actual text may be larger or
48-
* smaller than the difference between this and {@link #partialEndOffset},
48+
* smaller than the difference between this and {@link #partialStartOffset},
4949
* meaning a reduction or increase, respectively, in the total text.
5050
*/
5151
public int partialEndOffset;

core/java/android/widget/Editor.java

Lines changed: 69 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import android.text.Spanned;
4848
import android.text.StaticLayout;
4949
import android.text.TextUtils;
50-
import android.text.TextWatcher;
5150
import android.text.method.KeyListener;
5251
import android.text.method.MetaKeyKeyListener;
5352
import android.text.method.MovementMethod;
@@ -183,8 +182,6 @@ public class Editor {
183182

184183
Editor(TextView textView) {
185184
mTextView = textView;
186-
mEasyEditSpanController = new EasyEditSpanController();
187-
mTextView.addTextChangedListener(mEasyEditSpanController);
188185
}
189186

190187
void onAttachedToWindow() {
@@ -1120,7 +1117,7 @@ boolean reportExtractedText() {
11201117
if (contentChanged || ims.mSelectionModeChanged) {
11211118
ims.mContentChanged = false;
11221119
ims.mSelectionModeChanged = false;
1123-
final ExtractedTextRequest req = ims.mExtracting;
1120+
final ExtractedTextRequest req = ims.mExtractedTextRequest;
11241121
if (req != null) {
11251122
InputMethodManager imm = InputMethodManager.peekInstance();
11261123
if (imm != null) {
@@ -1132,13 +1129,14 @@ boolean reportExtractedText() {
11321129
ims.mChangedStart = EXTRACT_NOTHING;
11331130
}
11341131
if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
1135-
ims.mChangedDelta, ims.mTmpExtracted)) {
1132+
ims.mChangedDelta, ims.mExtractedText)) {
11361133
if (TextView.DEBUG_EXTRACT) Log.v(TextView.LOG_TAG,
11371134
"Reporting extracted start=" +
1138-
ims.mTmpExtracted.partialStartOffset +
1139-
" end=" + ims.mTmpExtracted.partialEndOffset +
1140-
": " + ims.mTmpExtracted.text);
1141-
imm.updateExtractedText(mTextView, req.token, ims.mTmpExtracted);
1135+
ims.mExtractedText.partialStartOffset +
1136+
" end=" + ims.mExtractedText.partialEndOffset +
1137+
": " + ims.mExtractedText.text);
1138+
1139+
imm.updateExtractedText(mTextView, req.token, ims.mExtractedText);
11421140
ims.mChangedStart = EXTRACT_UNKNOWN;
11431141
ims.mChangedEnd = EXTRACT_UNKNOWN;
11441142
ims.mChangedDelta = 0;
@@ -1734,138 +1732,92 @@ void onDrop(DragEvent event) {
17341732
}
17351733
}
17361734

1735+
public void addSpanWatchers(Spannable text) {
1736+
final int textLength = text.length();
1737+
1738+
if (mKeyListener != null) {
1739+
text.setSpan(mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
1740+
}
1741+
1742+
if (mEasyEditSpanController == null) {
1743+
mEasyEditSpanController = new EasyEditSpanController();
1744+
}
1745+
text.setSpan(mEasyEditSpanController, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
1746+
}
1747+
17371748
/**
17381749
* Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
17391750
* pop-up should be displayed.
17401751
*/
1741-
class EasyEditSpanController implements TextWatcher {
1752+
class EasyEditSpanController implements SpanWatcher {
17421753

17431754
private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
17441755

17451756
private EasyEditPopupWindow mPopupWindow;
17461757

1747-
private EasyEditSpan mEasyEditSpan;
1748-
17491758
private Runnable mHidePopup;
17501759

1751-
public void hide() {
1752-
if (mPopupWindow != null) {
1753-
mPopupWindow.hide();
1754-
mTextView.removeCallbacks(mHidePopup);
1755-
}
1756-
removeSpans(mTextView.getText());
1757-
mEasyEditSpan = null;
1758-
}
1759-
1760-
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1761-
// Intentionally empty
1762-
}
1763-
1764-
public void afterTextChanged(Editable s) {
1765-
// Intentionally empty
1766-
}
1760+
@Override
1761+
public void onSpanAdded(Spannable text, Object span, int start, int end) {
1762+
if (span instanceof EasyEditSpan) {
1763+
if (mPopupWindow == null) {
1764+
mPopupWindow = new EasyEditPopupWindow();
1765+
mHidePopup = new Runnable() {
1766+
@Override
1767+
public void run() {
1768+
hide();
1769+
}
1770+
};
1771+
}
17671772

1768-
/**
1769-
* Monitors the changes in the text.
1770-
*
1771-
* <p>{@link SpanWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
1772-
* as the notifications are not sent when a spannable (with spans) is inserted.
1773-
*/
1774-
public void onTextChanged(CharSequence buffer, int start, int before, int after) {
1775-
adjustSpans(buffer, start, after);
1773+
// Make sure there is only at most one EasyEditSpan in the text
1774+
if (mPopupWindow.mEasyEditSpan != null) {
1775+
text.removeSpan(mPopupWindow.mEasyEditSpan);
1776+
}
17761777

1777-
if (mTextView.getWindowVisibility() != View.VISIBLE) {
1778-
// The window is not visible yet, ignore the text change.
1779-
return;
1780-
}
1778+
mPopupWindow.setEasyEditSpan((EasyEditSpan) span);
17811779

1782-
if (mTextView.getLayout() == null) {
1783-
// The view has not been layout yet, ignore the text change
1784-
return;
1785-
}
1786-
1787-
InputMethodManager imm = InputMethodManager.peekInstance();
1788-
if (!(mTextView instanceof ExtractEditText) && imm != null && imm.isFullscreenMode()) {
1789-
// The input is in extract mode. We do not have to handle the easy edit in the
1790-
// original TextView, as the ExtractEditText will do
1791-
return;
1792-
}
1780+
if (mTextView.getWindowVisibility() != View.VISIBLE) {
1781+
// The window is not visible yet, ignore the text change.
1782+
return;
1783+
}
17931784

1794-
// Remove the current easy edit span, as the text changed, and remove the pop-up
1795-
// (if any)
1796-
if (mEasyEditSpan != null) {
1797-
if (buffer instanceof Spannable) {
1798-
((Spannable) buffer).removeSpan(mEasyEditSpan);
1785+
if (mTextView.getLayout() == null) {
1786+
// The view has not been laid out yet, ignore the text change
1787+
return;
17991788
}
1800-
mEasyEditSpan = null;
1801-
}
1802-
if (mPopupWindow != null && mPopupWindow.isShowing()) {
1803-
mPopupWindow.hide();
1804-
}
18051789

1806-
// Display the new easy edit span (if any).
1807-
if (buffer instanceof Spanned) {
1808-
mEasyEditSpan = getSpan((Spanned) buffer);
1809-
if (mEasyEditSpan != null) {
1810-
if (mPopupWindow == null) {
1811-
mPopupWindow = new EasyEditPopupWindow();
1812-
mHidePopup = new Runnable() {
1813-
@Override
1814-
public void run() {
1815-
hide();
1816-
}
1817-
};
1818-
}
1819-
mPopupWindow.show(mEasyEditSpan);
1820-
mTextView.removeCallbacks(mHidePopup);
1821-
mTextView.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
1790+
if (extractedTextModeWillBeStarted()) {
1791+
// The input is in extract mode. Do not handle the easy edit in
1792+
// the original TextView, as the ExtractEditText will do
1793+
return;
18221794
}
1795+
1796+
mPopupWindow.show();
1797+
mTextView.removeCallbacks(mHidePopup);
1798+
mTextView.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
18231799
}
18241800
}
18251801

1826-
/**
1827-
* Adjusts the spans by removing all of them except the last one.
1828-
*/
1829-
private void adjustSpans(CharSequence buffer, int start, int after) {
1830-
// This method enforces that only one easy edit span is attached to the text.
1831-
// A better way to enforce this would be to listen for onSpanAdded, but this method
1832-
// cannot be used in this scenario as no notification is triggered when a text with
1833-
// spans is inserted into a text.
1834-
if (buffer instanceof Spannable) {
1835-
Spannable spannable = (Spannable) buffer;
1836-
EasyEditSpan[] spans = spannable.getSpans(start, start + after, EasyEditSpan.class);
1837-
if (spans.length > 0) {
1838-
// Assuming there was only one EasyEditSpan before, we only need check to
1839-
// check for a duplicate if a new one is found in the modified interval
1840-
spans = spannable.getSpans(0, spannable.length(), EasyEditSpan.class);
1841-
for (int i = 1; i < spans.length; i++) {
1842-
spannable.removeSpan(spans[i]);
1843-
}
1844-
}
1802+
@Override
1803+
public void onSpanRemoved(Spannable text, Object span, int start, int end) {
1804+
if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
1805+
hide();
18451806
}
18461807
}
18471808

1848-
/**
1849-
* Removes all the {@link EasyEditSpan} currently attached.
1850-
*/
1851-
private void removeSpans(CharSequence buffer) {
1852-
if (buffer instanceof Spannable) {
1853-
Spannable spannable = (Spannable) buffer;
1854-
EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
1855-
EasyEditSpan.class);
1856-
for (int i = 0; i < spans.length; i++) {
1857-
spannable.removeSpan(spans[i]);
1858-
}
1809+
@Override
1810+
public void onSpanChanged(Spannable text, Object span, int previousStart, int previousEnd,
1811+
int newStart, int newEnd) {
1812+
if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
1813+
text.removeSpan(mPopupWindow.mEasyEditSpan);
18591814
}
18601815
}
18611816

1862-
private EasyEditSpan getSpan(Spanned spanned) {
1863-
EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
1864-
EasyEditSpan.class);
1865-
if (easyEditSpans.length == 0) {
1866-
return null;
1867-
} else {
1868-
return easyEditSpans[0];
1817+
public void hide() {
1818+
if (mPopupWindow != null) {
1819+
mPopupWindow.hide();
1820+
mTextView.removeCallbacks(mHidePopup);
18691821
}
18701822
}
18711823
}
@@ -1910,9 +1862,8 @@ protected void initContentView() {
19101862
mContentView.addView(mDeleteTextView);
19111863
}
19121864

1913-
public void show(EasyEditSpan easyEditSpan) {
1865+
public void setEasyEditSpan(EasyEditSpan easyEditSpan) {
19141866
mEasyEditSpan = easyEditSpan;
1915-
super.show();
19161867
}
19171868

19181869
@Override
@@ -3738,8 +3689,8 @@ static class InputMethodState {
37383689
Rect mCursorRectInWindow = new Rect();
37393690
RectF mTmpRectF = new RectF();
37403691
float[] mTmpOffset = new float[2];
3741-
ExtractedTextRequest mExtracting;
3742-
final ExtractedText mTmpExtracted = new ExtractedText();
3692+
ExtractedTextRequest mExtractedTextRequest;
3693+
final ExtractedText mExtractedText = new ExtractedText();
37433694
int mBatchEditNesting;
37443695
boolean mCursorChanged;
37453696
boolean mSelectionModeChanged;

core/java/android/widget/SpellChecker.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,20 +207,21 @@ public void onSelectionChanged() {
207207

208208
public void spellCheck(int start, int end) {
209209
final Locale locale = mTextView.getTextServicesLocale();
210+
final boolean isSessionActive = isSessionActive();
210211
if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
211212
setLocale(locale);
212213
// Re-check the entire text
213214
start = 0;
214215
end = mTextView.getText().length();
215216
} else {
216217
final boolean spellCheckerActivated = mTextServicesManager.isSpellCheckerEnabled();
217-
if (isSessionActive() != spellCheckerActivated) {
218+
if (isSessionActive != spellCheckerActivated) {
218219
// Spell checker has been turned of or off since last spellCheck
219220
resetSession();
220221
}
221222
}
222223

223-
if (!isSessionActive()) return;
224+
if (!isSessionActive) return;
224225

225226
// Find first available SpellParser from pool
226227
final int length = mSpellParsers.length;

core/java/android/widget/TextView.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3176,22 +3176,19 @@ private void setText(CharSequence text, BufferType type,
31763176
if (text instanceof Spannable && !mAllowTransformationLengthChange) {
31773177
Spannable sp = (Spannable) text;
31783178

3179-
// Remove any ChangeWatchers that might have come
3180-
// from other TextViews.
3179+
// Remove any ChangeWatchers that might have come from other TextViews.
31813180
final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
31823181
final int count = watchers.length;
3183-
for (int i = 0; i < count; i++)
3182+
for (int i = 0; i < count; i++) {
31843183
sp.removeSpan(watchers[i]);
3184+
}
31853185

3186-
if (mChangeWatcher == null)
3187-
mChangeWatcher = new ChangeWatcher();
3186+
if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
31883187

31893188
sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
31903189
(CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
31913190

3192-
if (mEditor != null && getEditor().mKeyListener != null) {
3193-
sp.setSpan(getEditor().mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3194-
}
3191+
if (mEditor != null) mEditor.addSpanWatchers(sp);
31953192

31963193
if (mTransformation != null) {
31973194
sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
@@ -5231,7 +5228,7 @@ public void setExtractedText(ExtractedText text) {
52315228
*/
52325229
public void setExtracting(ExtractedTextRequest req) {
52335230
if (getEditor().mInputMethodState != null) {
5234-
getEditor().mInputMethodState.mExtracting = req;
5231+
getEditor().mInputMethodState.mExtractedTextRequest = req;
52355232
}
52365233
// This would stop a possible selection mode, but no such mode is started in case
52375234
// extracted mode will start. Some text is selected though, and will trigger an action mode
@@ -6876,7 +6873,7 @@ void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd
68766873
if (what instanceof ParcelableSpan) {
68776874
// If this is a span that can be sent to a remote process,
68786875
// the current extract editor would be interested in it.
6879-
if (ims != null && ims.mExtracting != null) {
6876+
if (ims != null && ims.mExtractedTextRequest != null) {
68806877
if (ims.mBatchEditNesting != 0) {
68816878
if (oldStart >= 0) {
68826879
if (ims.mChangedStart > oldStart) {
@@ -6897,7 +6894,7 @@ void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd
68976894
} else {
68986895
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
68996896
+ oldStart + "-" + oldEnd + ","
6900-
+ newStart + "-" + newEnd + what);
6897+
+ newStart + "-" + newEnd + " " + what);
69016898
ims.mContentChanged = true;
69026899
}
69036900
}

0 commit comments

Comments
 (0)