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

Commit 0b7d747e authored by Gilles Debunne's avatar Gilles Debunne Committed by Android (Google) Code Review
Browse files

Merge "Editor uses a SpanWatcher to track EasyEditSpans"

parents db41553e c62589cb
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ public class ExtractedText implements Parcelable {
    /**
     * If the content is a report of a partial text change, this is the offset
     * where the change ends.  Note that the actual text may be larger or
     * smaller than the difference between this and {@link #partialEndOffset},
     * smaller than the difference between this and {@link #partialStartOffset},
     * meaning a reduction or increase, respectively, in the total text.
     */
    public int partialEndOffset;
+69 −118
Original line number Diff line number Diff line
@@ -47,7 +47,6 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
@@ -184,8 +183,6 @@ public class Editor {

    Editor(TextView textView) {
        mTextView = textView;
        mEasyEditSpanController = new EasyEditSpanController();
        mTextView.addTextChangedListener(mEasyEditSpanController);
    }

    void onAttachedToWindow() {
@@ -1121,7 +1118,7 @@ public class Editor {
            if (contentChanged || ims.mSelectionModeChanged) {
                ims.mContentChanged = false;
                ims.mSelectionModeChanged = false;
                final ExtractedTextRequest req = ims.mExtracting;
                final ExtractedTextRequest req = ims.mExtractedTextRequest;
                if (req != null) {
                    InputMethodManager imm = InputMethodManager.peekInstance();
                    if (imm != null) {
@@ -1133,13 +1130,14 @@ public class Editor {
                            ims.mChangedStart = EXTRACT_NOTHING;
                        }
                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
                                ims.mChangedDelta, ims.mTmpExtracted)) {
                                ims.mChangedDelta, ims.mExtractedText)) {
                            if (TextView.DEBUG_EXTRACT) Log.v(TextView.LOG_TAG,
                                    "Reporting extracted start=" +
                                    ims.mTmpExtracted.partialStartOffset +
                                    " end=" + ims.mTmpExtracted.partialEndOffset +
                                    ": " + ims.mTmpExtracted.text);
                            imm.updateExtractedText(mTextView, req.token, ims.mTmpExtracted);
                                    ims.mExtractedText.partialStartOffset +
                                    " end=" + ims.mExtractedText.partialEndOffset +
                                    ": " + ims.mExtractedText.text);

                            imm.updateExtractedText(mTextView, req.token, ims.mExtractedText);
                            ims.mChangedStart = EXTRACT_UNKNOWN;
                            ims.mChangedEnd = EXTRACT_UNKNOWN;
                            ims.mChangedDelta = 0;
@@ -1775,45 +1773,50 @@ public class Editor {
        }
    }

    public void addSpanWatchers(Spannable text) {
        final int textLength = text.length();

        if (mKeyListener != null) {
            text.setSpan(mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }

        if (mEasyEditSpanController == null) {
            mEasyEditSpanController = new EasyEditSpanController();
        }
        text.setSpan(mEasyEditSpanController, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
    }

    /**
     * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
     * pop-up should be displayed.
     */
    class EasyEditSpanController implements TextWatcher {
    class EasyEditSpanController implements SpanWatcher {

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

        private EasyEditPopupWindow mPopupWindow;

        private EasyEditSpan mEasyEditSpan;

        private Runnable mHidePopup;

        public void hide() {
            if (mPopupWindow != null) {
                mPopupWindow.hide();
                mTextView.removeCallbacks(mHidePopup);
            }
            removeSpans(mTextView.getText());
            mEasyEditSpan = null;
        @Override
        public void onSpanAdded(Spannable text, Object span, int start, int end) {
            if (span instanceof EasyEditSpan) {
                if (mPopupWindow == null) {
                    mPopupWindow = new EasyEditPopupWindow();
                    mHidePopup = new Runnable() {
                        @Override
                        public void run() {
                            hide();
                        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // Intentionally empty
                    };
                }

        public void afterTextChanged(Editable s) {
            // Intentionally empty
                // Make sure there is only at most one EasyEditSpan in the text
                if (mPopupWindow.mEasyEditSpan != null) {
                    text.removeSpan(mPopupWindow.mEasyEditSpan);
                }

        /**
         * Monitors the changes in the text.
         *
         * <p>{@link SpanWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
         * as the notifications are not sent when a spannable (with spans) is inserted.
         */
        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
            adjustSpans(buffer, start, after);
                mPopupWindow.setEasyEditSpan((EasyEditSpan) span);

                if (mTextView.getWindowVisibility() != View.VISIBLE) {
                    // The window is not visible yet, ignore the text change.
@@ -1821,92 +1824,41 @@ public class Editor {
                }

                if (mTextView.getLayout() == null) {
                // The view has not been layout yet, ignore the text change
                    // The view has not been laid out yet, ignore the text change
                    return;
                }

            InputMethodManager imm = InputMethodManager.peekInstance();
            if (!(mTextView 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
                if (extractedTextModeWillBeStarted()) {
                    // The input is in extract mode. Do not 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 (buffer instanceof Spannable) {
                    ((Spannable) buffer).removeSpan(mEasyEditSpan);
                }
                mEasyEditSpan = null;
            }
            if (mPopupWindow != null && mPopupWindow.isShowing()) {
                mPopupWindow.hide();
            }

            // Display the new easy edit span (if any).
            if (buffer instanceof Spanned) {
                mEasyEditSpan = getSpan((Spanned) buffer);
                if (mEasyEditSpan != null) {
                    if (mPopupWindow == null) {
                        mPopupWindow = new EasyEditPopupWindow();
                        mHidePopup = new Runnable() {
                            @Override
                            public void run() {
                                hide();
                            }
                        };
                    }
                    mPopupWindow.show(mEasyEditSpan);
                mPopupWindow.show();
                mTextView.removeCallbacks(mHidePopup);
                mTextView.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
            }
        }
        }

        /**
         * Adjusts the spans by removing all of them except the last one.
         */
        private void adjustSpans(CharSequence buffer, int start, int after) {
            // 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(start, start + after, EasyEditSpan.class);
                if (spans.length > 0) {
                    // Assuming there was only one EasyEditSpan before, we only need check to
                    // check for a duplicate if a new one is found in the modified interval
                    spans = spannable.getSpans(0, spannable.length(),  EasyEditSpan.class);
                    for (int i = 1; i < spans.length; i++) {
                        spannable.removeSpan(spans[i]);
                    }
                }
        @Override
        public void onSpanRemoved(Spannable text, Object span, int start, int end) {
            if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
                hide();
            }
        }

        /**
         * 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]);
                }
        @Override
        public void onSpanChanged(Spannable text, Object span, int previousStart, int previousEnd,
                int newStart, int newEnd) {
            if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
                text.removeSpan(mPopupWindow.mEasyEditSpan);
            }
        }

        private EasyEditSpan getSpan(Spanned spanned) {
            EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
                    EasyEditSpan.class);
            if (easyEditSpans.length == 0) {
                return null;
            } else {
                return easyEditSpans[0];
        public void hide() {
            if (mPopupWindow != null) {
                mPopupWindow.hide();
                mTextView.removeCallbacks(mHidePopup);
            }
        }
    }
@@ -1951,9 +1903,8 @@ public class Editor {
            mContentView.addView(mDeleteTextView);
        }

        public void show(EasyEditSpan easyEditSpan) {
        public void setEasyEditSpan(EasyEditSpan easyEditSpan) {
            mEasyEditSpan = easyEditSpan;
            super.show();
        }

        @Override
@@ -3780,8 +3731,8 @@ public class Editor {
        Rect mCursorRectInWindow = new Rect();
        RectF mTmpRectF = new RectF();
        float[] mTmpOffset = new float[2];
        ExtractedTextRequest mExtracting;
        final ExtractedText mTmpExtracted = new ExtractedText();
        ExtractedTextRequest mExtractedTextRequest;
        final ExtractedText mExtractedText = new ExtractedText();
        int mBatchEditNesting;
        boolean mCursorChanged;
        boolean mSelectionModeChanged;
+3 −2
Original line number Diff line number Diff line
@@ -212,6 +212,7 @@ public class SpellChecker implements SpellCheckerSessionListener {

    public void spellCheck(int start, int end) {
        final Locale locale = mTextView.getTextServicesLocale();
        final boolean isSessionActive = isSessionActive();
        if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
            setLocale(locale);
            // Re-check the entire text
@@ -219,13 +220,13 @@ public class SpellChecker implements SpellCheckerSessionListener {
            end = mTextView.getText().length();
        } else {
            final boolean spellCheckerActivated = mTextServicesManager.isSpellCheckerEnabled();
            if (isSessionActive() != spellCheckerActivated) {
            if (isSessionActive != spellCheckerActivated) {
                // Spell checker has been turned of or off since last spellCheck
                resetSession();
            }
        }

        if (!isSessionActive()) return;
        if (!isSessionActive) return;

        // Find first available SpellParser from pool
        final int length = mSpellParsers.length;
+8 −11
Original line number Diff line number Diff line
@@ -3200,22 +3200,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
            Spannable sp = (Spannable) text;

            // Remove any ChangeWatchers that might have come
            // from other TextViews.
            // Remove any ChangeWatchers that might have come from other TextViews.
            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
            final int count = watchers.length;
            for (int i = 0; i < count; i++)
            for (int i = 0; i < count; i++) {
                sp.removeSpan(watchers[i]);
            }

            if (mChangeWatcher == null)
                mChangeWatcher = new ChangeWatcher();
            if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();

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

            if (mEditor != null && getEditor().mKeyListener != null) {
                sp.setSpan(getEditor().mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
            }
            if (mEditor != null) mEditor.addSpanWatchers(sp);

            if (mTransformation != null) {
                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
@@ -5208,7 +5205,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     */
    public void setExtracting(ExtractedTextRequest req) {
        if (getEditor().mInputMethodState != null) {
            getEditor().mInputMethodState.mExtracting = req;
            getEditor().mInputMethodState.mExtractedTextRequest = req;
        }
        // This would stop a possible selection mode, but no such mode is started in case
        // extracted mode will start. Some text is selected though, and will trigger an action mode
@@ -6856,7 +6853,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        if (what instanceof ParcelableSpan) {
            // If this is a span that can be sent to a remote process,
            // the current extract editor would be interested in it.
            if (ims != null && ims.mExtracting != null) {
            if (ims != null && ims.mExtractedTextRequest != null) {
                if (ims.mBatchEditNesting != 0) {
                    if (oldStart >= 0) {
                        if (ims.mChangedStart > oldStart) {
@@ -6877,7 +6874,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                } else {
                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
                            + oldStart + "-" + oldEnd + ","
                            + newStart + "-" + newEnd + what);
                            + newStart + "-" + newEnd + " " + what);
                    ims.mContentChanged = true;
                }
            }