Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1564fc7c authored by Luca Zanolin's avatar Luca Zanolin
Browse files

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
parent 37c5cd6e
Loading
Loading
Loading
Loading
+116 −35
Original line number Diff line number Diff line
@@ -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 static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs

        private EditTextShortcutPopupWindow mPopupWindow;
        private EasyEditPopupWindow mPopupWindow;

        private EasyEditSpan mEditTextShortcutSpan;
        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);
                }
            }
        }

        private EasyEditSpan getSpan(Spanned spanned) {
            EasyEditSpan[] inputMethodSpans = spanned.getSpans(0, spanned.length(),
        /**
         * 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]);
                }
            }
        }

            if (inputMethodSpans.length == 0) {
        /**
         * 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[] easyEditSpans = spanned.getSpans(0, spanned.length(),
                    EasyEditSpan.class);
            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.