From 1564fc7c7eab89fb2d13719c0d4dc664d57b930e Mon Sep 17 00:00:00 2001 From: Luca Zanolin Date: Wed, 7 Sep 2011 00:01:28 +0100 Subject: [PATCH] Fix several issues with the "EasyEditSpan". - The easy edit span was displayed twice when in extracted mode. The orignal TextView now checks if it is in extra mode, and if so it does not display any pop-up - The easy edit span was displayed before the view was layout causing the application to crash. New feature: - the span is automatically hidden after a timeout I also renamed all the fields and classes to "EasyEdit...". There were still some field/class using an old name. Bug: 5255363 Bug: 5247453 Bug: 5246997 Change-Id: Ic9bf05d2525e2df9017c91344a687e8cb9105417 --- core/java/android/widget/TextView.java | 151 +++++++++++++++++++------ 1 file changed, 116 insertions(+), 35 deletions(-) diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index abf1281128236..efc82511381c1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -7591,7 +7591,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Hide the controllers if the amount of content changed if (before != after) { - hideControllers(); + // We do not hide the span controllers, as they can be added when a new text is + // inserted into the text view + hideCursorControllers(); } } @@ -7799,20 +7801,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related * pop-up should be displayed. */ - private class EditTextShortcutController { + private class EasyEditSpanController { - private EditTextShortcutPopupWindow mPopupWindow; + private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs - private EasyEditSpan mEditTextShortcutSpan; + private EasyEditPopupWindow mPopupWindow; + + private EasyEditSpan mEasyEditSpan; + + private Runnable mHidePopup; private void hide() { - if (mEditTextShortcutSpan != null) { + if (mPopupWindow != null) { mPopupWindow.hide(); - if (mText instanceof Spannable) { - ((Spannable) mText).removeSpan(mEditTextShortcutSpan); - } - mEditTextShortcutSpan = null; + TextView.this.removeCallbacks(mHidePopup); } + removeSpans(mText); + mEasyEditSpan = null; } /** @@ -7822,43 +7827,111 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * as the notifications are not sent when a spannable (with spans) is inserted. */ public void onTextChange(CharSequence buffer) { - if (mEditTextShortcutSpan != null) { - hide(); + adjustSpans(mText); + + if (getWindowVisibility() != View.VISIBLE) { + // The window is not visible yet, ignore the text change. + return; } + if (mLayout == null) { + // The view has not been layout yet, ignore the text change + return; + } + + InputMethodManager imm = InputMethodManager.peekInstance(); + if (!(TextView.this instanceof ExtractEditText) + && imm != null && imm.isFullscreenMode()) { + // The input is in extract mode. We do not have to handle the easy edit in the + // original TextView, as the ExtractEditText will do + return; + } + + // Remove the current easy edit span, as the text changed, and remove the pop-up + // (if any) + if (mEasyEditSpan != null) { + if (mText instanceof Spannable) { + ((Spannable) mText).removeSpan(mEasyEditSpan); + } + mEasyEditSpan = null; + } + if (mPopupWindow != null && mPopupWindow.isShowing()) { + mPopupWindow.hide(); + } + + // Display the new easy edit span (if any). if (buffer instanceof Spanned) { - mEditTextShortcutSpan = getSpan((Spanned) buffer); - if (mEditTextShortcutSpan != null) { + mEasyEditSpan = getSpan((Spanned) buffer); + if (mEasyEditSpan != null) { if (mPopupWindow == null) { - mPopupWindow = new EditTextShortcutPopupWindow(); + mPopupWindow = new EasyEditPopupWindow(); + mHidePopup = new Runnable() { + @Override + public void run() { + hide(); + } + }; } - mPopupWindow.show(mEditTextShortcutSpan); + mPopupWindow.show(mEasyEditSpan); + TextView.this.removeCallbacks(mHidePopup); + TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS); + } + } + } + + /** + * Adjusts the spans by removing all of them except the last one. + */ + private void adjustSpans(CharSequence buffer) { + // This method enforces that only one easy edit span is attached to the text. + // A better way to enforce this would be to listen for onSpanAdded, but this method + // cannot be used in this scenario as no notification is triggered when a text with + // spans is inserted into a text. + if (buffer instanceof Spannable) { + Spannable spannable = (Spannable) buffer; + EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(), + EasyEditSpan.class); + for (int i = 0; i < spans.length - 1; i++) { + spannable.removeSpan(spans[i]); + } + } + } + + /** + * Removes all the {@link EasyEditSpan} currently attached. + */ + private void removeSpans(CharSequence buffer) { + if (buffer instanceof Spannable) { + Spannable spannable = (Spannable) buffer; + EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(), + EasyEditSpan.class); + for (int i = 0; i < spans.length; i++) { + spannable.removeSpan(spans[i]); } } } private EasyEditSpan getSpan(Spanned spanned) { - EasyEditSpan[] inputMethodSpans = spanned.getSpans(0, spanned.length(), + EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(), EasyEditSpan.class); - - if (inputMethodSpans.length == 0) { + if (easyEditSpans.length == 0) { return null; } else { - return inputMethodSpans[0]; + return easyEditSpans[0]; } } } /** * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled - * by {@link EditTextShortcutController}. + * by {@link EasyEditSpanController}. */ - private class EditTextShortcutPopupWindow extends PinnedPopupWindow + private class EasyEditPopupWindow extends PinnedPopupWindow implements OnClickListener { private static final int POPUP_TEXT_LAYOUT = com.android.internal.R.layout.text_edit_action_popup_text; private TextView mDeleteTextView; - private EasyEditSpan mEditTextShortcutSpan; + private EasyEditSpan mEasyEditSpan; @Override protected void createPopupWindow() { @@ -7889,8 +7962,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mContentView.addView(mDeleteTextView); } - public void show(EasyEditSpan inputMethodSpan) { - mEditTextShortcutSpan = inputMethodSpan; + public void show(EasyEditSpan easyEditSpan) { + mEasyEditSpan = easyEditSpan; super.show(); } @@ -7903,8 +7976,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void deleteText() { Editable editable = (Editable) mText; - int start = editable.getSpanStart(mEditTextShortcutSpan); - int end = editable.getSpanEnd(mEditTextShortcutSpan); + int start = editable.getSpanStart(mEasyEditSpan); + int end = editable.getSpanEnd(mEasyEditSpan); if (start >= 0 && end >= 0) { editable.delete(start, end); } @@ -7914,7 +7987,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected int getTextOffset() { // Place the pop-up at the end of the span Editable editable = (Editable) mText; - return editable.getSpanEnd(mEditTextShortcutSpan); + return editable.getSpanEnd(mEasyEditSpan); } @Override @@ -7933,10 +8006,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CharSequence mBeforeText; - private EditTextShortcutController mEditTextShortcutController; + private EasyEditSpanController mEasyEditSpanController; private ChangeWatcher() { - mEditTextShortcutController = new EditTextShortcutController(); + mEasyEditSpanController = new EasyEditSpanController(); } public void beforeTextChanged(CharSequence buffer, int start, @@ -7959,7 +8032,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " before=" + before + " after=" + after + ": " + buffer); TextView.this.handleTextChanged(buffer, start, before, after); - mEditTextShortcutController.onTextChange(buffer); + mEasyEditSpanController.onTextChange(buffer); if (AccessibilityManager.getInstance(mContext).isEnabled() && (isFocused() || isSelected() && isShown())) { @@ -7997,7 +8070,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void hideControllers() { - mEditTextShortcutController.hide(); + mEasyEditSpanController.hide(); } } @@ -9236,8 +9309,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private class PositionListener implements ViewTreeObserver.OnPreDrawListener { - // 3 handles, 2 ActionPopup (suggestionsPopup first hides the others) - private final int MAXIMUM_NUMBER_OF_LISTENERS = 5; + // 3 handles + // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others) + private final int MAXIMUM_NUMBER_OF_LISTENERS = 6; private TextViewPositionListener[] mPositionListeners = new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS]; private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS]; @@ -11022,14 +11096,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Hides the insertion controller and stops text selection mode, hiding the selection controller */ private void hideControllers() { - hideInsertionPointCursorController(); - stopSelectionActionMode(); + hideCursorControllers(); + hideSpanControllers(); + } + private void hideSpanControllers() { if (mChangeWatcher != null) { mChangeWatcher.hideControllers(); } } + private void hideCursorControllers() { + hideInsertionPointCursorController(); + stopSelectionActionMode(); + } + /** * Get the character offset closest to the specified absolute position. A typical use case is to * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.