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

Commit 4cab772b authored by Haoyu Zhang's avatar Haoyu Zhang Committed by Android (Google) Code Review
Browse files

Merge "Make the insert mode highlight sticky" into main

parents 3c0bf9d9 f51becbf
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -117,6 +117,13 @@ flag {
  bug: "314254153"
}

flag {
  name: "insert_mode_highlight_range"
  namespace: "text"
  description: "Make the highlight range stick after editing, this handles the corner cases where the entire text is replaced with itself(or transformed by developer) after each editing."
  bug: "355137282"
}

flag {
  name: "insert_mode_not_update_selection"
  namespace: "text"
+19 −6
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.view.View;

import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.text.flags.Flags;

import java.lang.reflect.Array;

@@ -170,22 +171,34 @@ public class InsertModeTransformationMethod implements TransformationMethod, Tex
            if (start + before <= mStart) {
                // The text change is before the highlight start, move the highlight start.
                mStart += diff;
            } else {
                if (Flags.insertModeHighlightRange()) {
                    // The text change covers the highlight start. Don't change the start except
                    // when it's out of range.
                    mStart = Math.min(mStart, s.length());
                } else {
                    // The text change covers the highlight start. Extend the highlight start to the
                    // change start. This should be a rare case.
                    mStart = start;
                }
            }
        }

        if (start + before <= mEnd) {
            // The text change is before the highlight end, move the highlight end.
            mEnd += diff;
        } else if (start < mEnd) {
            if (Flags.insertModeHighlightRange()) {
                // The text change covers the highlight end. Don't change the end except when it's
                // out of range.
                mEnd = Math.min(mEnd, s.length());
            } else {
                // The text change covers the highlight end. Extend the highlight end to the
                // change end. This should be a rare case.
                mEnd = start + count;
            }
        }
    }

    @Override
    public void afterTextChanged(Editable s) { }
+248 −4
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -30,7 +34,10 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.text.flags.Flags;

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -41,6 +48,9 @@ public class InsertModeTransformationMethodTest {
    private static View sView;
    private static final String TEXT = "abc def";

    @Rule
    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    @BeforeClass
    public static void setupClass() {
        final Context context = InstrumentationRegistry.getTargetContext();
@@ -76,11 +86,13 @@ public class InsertModeTransformationMethodTest {
    }

    @Test
    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_charAt_editing() {
        transformedText_charAt_editing(false, "\n\n");
    }

    @Test
    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_charAt_singleLine_editing() {
        transformedText_charAt_editing(true, "\uFFFD");
    }
@@ -131,6 +143,64 @@ public class InsertModeTransformationMethodTest {
        assertCharSequence(transformedText, "abc de" + placeholder + "f");
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_charAt_editing_stickyHighlightRange() {
        transformedText_charAt_editing_stickyHighlightRange(false, "\n\n");
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_charAt_singleLine_editing_stickyHighlightRange() {
        transformedText_charAt_editing_stickyHighlightRange(true, "\uFFFD");
    }

    private void transformedText_charAt_editing_stickyHighlightRange(boolean singleLine,
            String placeholder) {
        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
        final InsertModeTransformationMethod transformationMethod =
                new InsertModeTransformationMethod(3, singleLine, null);
        final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        assertCharSequence(transformedText,  "abc" + placeholder + " def");

        // original text is "abcxx def" after insertion.
        text.insert(3, "xx");
        assertCharSequence(transformedText, "abcxx" + placeholder + " def");

        // original text is "abcxx vvdef" after insertion.
        text.insert(6, "vv");
        assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");

        // original text is "abc vvdef" after deletion.
        text.delete(3, 5);
        assertCharSequence(transformedText, "abc" + placeholder + " vvdef");

        // original text is "abc def" after deletion.
        text.delete(4, 6);
        assertCharSequence(transformedText, "abc" + placeholder + " def");

        // original text is "abdef" after deletion.
        // deletion range covers the placeholder's insertion point. It'll try to stay the same,
        // which is still at index 3.
        text.delete(2, 4);
        assertCharSequence(transformedText, "abd" + placeholder + "ef");

        // original text is "axxdef" after replace.
        // this time the replaced range is ahead of the placeholder's insertion point. It updates to
        // index 4.
        text.replace(1, 2, "xx");
        assertCharSequence(transformedText, "axxd" + placeholder + "ef");

        // original text is "ax" after replace.
        // the deleted range covers the placeholder's insertion point. It tries to stay at index 4.
        // However, 4 out of bounds now. So placeholder is inserted at the end of the string.
        text.delete(2, 6);
        assertCharSequence(transformedText, "ax" + placeholder);
    }

    @Test
    public void transformedText_subSequence() {
        for (int offset = 0; offset < TEXT.length(); ++offset) {
@@ -697,7 +767,7 @@ public class InsertModeTransformationMethodTest {
    }

    @Test
    public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
    public void transformedText_getHighlightStartAndEnd_singleLine_insertion() {
        transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
    }

@@ -751,16 +821,18 @@ public class InsertModeTransformationMethodTest {
    }

    @Test
    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_deletion() {
        transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
    }

    @Test
    public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_singleLine_deletion() {
        transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
    }

    public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
    private void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
            String placeholder) {
        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
        final InsertModeTransformationMethod transformationMethod =
@@ -816,14 +888,93 @@ public class InsertModeTransformationMethodTest {
        assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange() {
        transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(false, "\n\n");
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_singleLine_deletion_stickyHighlightRange() {
        transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(true, "\uFDDD");
    }

    private void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(
            boolean singleLine, String placeholder) {
        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
        final InsertModeTransformationMethod transformationMethod =
                new InsertModeTransformationMethod(3, singleLine, null);
        final InsertModeTransformationMethod.TransformedText transformedText =
                (InsertModeTransformationMethod.TransformedText) transformationMethod
                        .getTransformation(text, sView);
        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        // note: the placeholder text is also highlighted.
        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());

        // original text is "abcxxxxxx def" after insertion.
        // the placeholder is now inserted at index 9.
        // the highlight start is still 3.
        // the highlight end now is 9 + placeholder.length().
        text.insert(3, "xxxxxx");
        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());

        // original text is "abxxxxxx def" after deletion.
        // the placeholder is now inserted at index 6.
        // the highlight start is 2, since the deletion happens before the highlight range.
        // the highlight end now is 8 + placeholder.length().
        text.delete(2, 3);
        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());

        // original text is "abxxx def" after deletion.
        // the placeholder is now inserted at index 5.
        // the highlight start is still 2, since the deletion happens in the highlight range.
        // the highlight end now is 5 + placeholder.length().
        text.delete(2, 5);
        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());

        // original text is "abxxx d" after deletion.
        // the placeholder is now inserted at index 5.
        // the highlight start is still 2, since the deletion happens after the highlight range.
        // the highlight end now is still 5 + placeholder.length().
        text.delete(7, 9);
        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());

        // original text is "axx d" after deletion.
        // the placeholder is now inserted at index 3.
        // the highlight start is at 2, since the deletion range covers the start.
        // the highlight end is 3 + placeholder.length().
        text.delete(1, 3);
        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());

        // original text is "ax" after deletion.
        // the placeholder is now inserted at index 2.
        // the highlight start is at 2.
        // the highlight end is 2 + placeholder.length(). It wants to stay at 3, but it'll be out
        // of bounds, so it'll be 2 instead.
        text.delete(2, 5);
        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(2 + placeholder.length());
    }


    @Test
    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_replace() {
        transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
    }

    @Test
    public void transformedText_getHighlightStartAndEnd_insertion__replace() {
    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_singleLine_replace() {
        transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
    }

@@ -908,6 +1059,99 @@ public class InsertModeTransformationMethodTest {
        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange() {
        transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(false, "\n\n");
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
    public void transformedText_getHighlightStartAndEnd_singleLine_replace_stickyHighlightRange() {
        transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(true, "\uFDDD");
    }

    private void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(
            boolean singleLine, String placeholder) {
        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
        final InsertModeTransformationMethod transformationMethod =
                new InsertModeTransformationMethod(3, singleLine, null);
        final InsertModeTransformationMethod.TransformedText transformedText =
                (InsertModeTransformationMethod.TransformedText) transformationMethod
                        .getTransformation(text, sView);
        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        // note: the placeholder text is also highlighted.
        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());

        // original text is "abcxxxxxx def" after insertion.
        // the placeholder is now inserted at index 9.
        // the highlight start is still 3.
        // the highlight end now is 9 + placeholder.length().
        text.insert(3, "xxxxxx");
        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());

        // original text is "abvvxxxxxx def" after replace.
        // the replacement happens before the highlight range; highlight range is offset by 1
        // the placeholder is now inserted at index 10,
        // the highlight start is 4.
        // the highlight end is 10 + placeholder.length().
        text.replace(2, 3, "vv");
        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());

        // original text is "abvvxxx def" after replace.
        // the replacement happens in the highlight range; highlight end is offset by -3
        // the placeholder is now inserted at index 7,
        // the highlight start is still 4.
        // the highlight end is 7 + placeholder.length().
        text.replace(5, 9, "x");
        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());

        // original text is "abvvxxxvvv" after replace.
        // the replacement happens after the highlight range; highlight is not changed
        // the placeholder is now inserted at index 7,
        // the highlight start is still 4.
        // the highlight end is 7 + placeholder.length().
        text.replace(7, 11, "vvv");
        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());

        // original text is "abxxxxvvv" after replace.
        // the replacement covers the highlight start; highlight start stays the same;
        // highlight end is offset by -1
        // the placeholder is now inserted at index 6,
        // the highlight start is 4.
        // the highlight end is 6 + placeholder.length().
        text.replace(2, 5, "xx");
        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());

        // original text is "abxxxxxvv" after replace.
        // the replacement covers the highlight end; highlight end stays the same;
        // highlight start stays the same
        // the placeholder is now inserted at index 6,
        // the highlight start is 2.
        // the highlight end is 6 + placeholder.length().
        text.replace(5, 7, "xx");
        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());

        // original text is "axxv" after replace.
        // the replacement covers the highlight range; highlight start stays the same.
        // highlight end shrink to the text length.
        // the placeholder is now inserted at index 3,
        // the highlight start is 2.
        // the highlight end is 4 + placeholder.length().
        text.replace(1, 8, "xx");
        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
        assertThat(transformedText.getHighlightEnd()).isEqualTo(4 + placeholder.length());
    }

    private static  <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
            Class<T> type) {
        int currentTransition = 0;