chromium/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.omnibox;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.spy;

import android.app.Activity;
import android.content.Context;
import android.text.Editable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.LinearLayout;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowAccessibilityManager;
import org.robolectric.shadows.ShadowLog;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.omnibox.test.R;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.omnibox.OmniboxFeatures;
import org.chromium.ui.accessibility.AccessibilityState;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A robolectric test for {@link AutocompleteEditText} class. TODO(changwan): switch to
 * ParameterizedRobolectricTest once crbug.com/733324 is fixed.
 */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class AutocompleteEditTextTest {
    private static final String TAG = "AutocompleteTest";

    private static final boolean DEBUG = false;

    private InOrder mInOrder;
    private TestAutocompleteEditText mAutocomplete;
    private LinearLayout mFocusPlaceHolder;

    private Context mContext;
    private InputConnection mInputConnection;
    private Verifier mVerifier;
    private boolean mIsShown;

    /**
     * A flag to tweak test expectations to deal with an OS bug.
     *
     * <p>{@code EditableInputConnection}, which {@link AutocompleteEditText} internally rely on,
     * has had <a href="https://issuetracker.google.com/issues/209958658">a bug</a> that it still
     * returns {@code true} from {@link InputConnection#endBatchEdit()} when its internal batch edit
     * count becomes {@code 0} as a result of invocation, which clearly conflicted with the spec.
     * There are several tests in this file that are unfortunately affected by this bug. In order to
     * abstract out such an OS issue from the actual test expectations, we will dynamically test if
     * the bug still exists or not in the test execution environment or not, and set {@code true} to
     * this flag if it is still there.
     *
     * <p>Until a new version of Android OS with <a
     * href="https://android-review.googlesource.com/c/platform/frameworks/base/+/1923058">the
     * fix</a> and a corresponding version of Robolectric become available in Chromium, this flag is
     * expected to be always {@code true}. Once they become available, you can either remove this
     * flag with assuming it's always {@code false} or test two different OS behaviors at the same
     * time by <a href="http://robolectric.org/configuring/">specifying multiple SDK versions</a> to
     * the test runner</a>.
     *
     * @see #testEditableInputConnectionEndBatchEditBug(Context)
     * @see #assertLastBatchEdit(boolean)
     */
    private boolean mHasEditableInputConnectionEndBatchEditBug;

    /**
     * Test if {@code EditableInputConnection} has a bug that it still returns {@code true} from
     * {@link InputConnection#endBatchEdit()} when its internal batch edit count becomes {@code 0}
     * as a result of invocation.
     *
     * <p>See https://issuetracker.google.com/issues/209958658 for details.
     *
     * @param context The {@link Context} to be used to initialize {@link EditText}.
     * @return {@code true} if the bug still exists. {@code false} otherwise.
     */
    private static boolean testEditableInputConnectionEndBatchEditBug(Context context) {
        EditText editText = new EditText(context);
        EditorInfo editorInfo = new EditorInfo();
        InputConnection editableInputConnection = editText.onCreateInputConnection(editorInfo);
        editableInputConnection.beginBatchEdit();
        // If this returns true, yes, the bug is still there!
        return editableInputConnection.endBatchEdit();
    }

    /**
     * A convenient helper method to assert the return value of {@link
     * InputConnection#endBatchEdit()} when its internal batch edit count becomes {@code 0}.
     *
     * @param result The return value of {@link InputConnection#endBatchEdit()}.
     * @see #mHasEditableInputConnectionEndBatchEditBug
     * @see #testEditableInputConnectionEndBatchEditBug(Context)
     */
    private void assertLastBatchEdit(boolean result) {
        if (mHasEditableInputConnectionEndBatchEditBug) {
            assertTrue(result);
        } else {
            assertFalse(result);
        }
    }

    // Limits the target of InOrder#verify.
    private static class Verifier {
        public void onAutocompleteTextStateChanged(boolean updateDisplay) {
            if (DEBUG) Log.i(TAG, "onAutocompleteTextStateChanged(%b)", updateDisplay);
        }

        public void onUpdateSelection(int selStart, int selEnd) {
            if (DEBUG) Log.i(TAG, "onUpdateSelection(%d, %d)", selStart, selEnd);
        }

        public void onPopulateAccessibilityEvent(
                int eventType,
                String text,
                String beforeText,
                int itemCount,
                int fromIndex,
                int toIndex,
                int removedCount,
                int addedCount) {
            if (DEBUG) {
                Log.i(
                        TAG,
                        "onPopulateAccessibilityEvent: TYP[%d] TXT[%s] BEF[%s] CNT[%d] "
                                + "FROM[%d] TO[%d] REM[%d] ADD[%d]",
                        eventType,
                        text,
                        beforeText,
                        itemCount,
                        fromIndex,
                        toIndex,
                        removedCount,
                        addedCount);
            }
        }
    }

    private class TestAutocompleteEditText extends AutocompleteEditText {
        private static final String JAVASCRIPT_SCHEME = "javascript:";

        private AtomicInteger mVerifierCallCount = new AtomicInteger();
        private AtomicInteger mAccessibilityVerifierCallCount = new AtomicInteger();
        private AtomicReference<String> mKeyboardPackageName =
                new AtomicReference<>("placeholder.ime");

        public TestAutocompleteEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
            if (DEBUG) Log.i(TAG, "TestAutocompleteEditText constructor");
        }

        @Override
        public void onAutocompleteTextStateChanged(boolean updateDisplay) {
            mVerifier.onAutocompleteTextStateChanged(updateDisplay);
            mVerifierCallCount.incrementAndGet();
        }

        @Override
        public void onUpdateSelectionForTesting(int selStart, int selEnd) {
            mVerifier.onUpdateSelection(selStart, selEnd);
            mVerifierCallCount.incrementAndGet();
        }

        @Override
        public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
            super.onPopulateAccessibilityEvent(event);
            mVerifier.onPopulateAccessibilityEvent(
                    event.getEventType(),
                    getText(event),
                    getBeforeText(event),
                    event.getItemCount(),
                    event.getFromIndex(),
                    event.getToIndex(),
                    event.getRemovedCount(),
                    event.getAddedCount());
            mAccessibilityVerifierCallCount.incrementAndGet();
        }

        private String getText(AccessibilityEvent event) {
            if (event.getText() != null && event.getText().size() > 0) {
                return event.getText().get(0).toString();
            }
            return "";
        }

        private String getBeforeText(AccessibilityEvent event) {
            return event.getBeforeText() == null ? "" : event.getBeforeText().toString();
        }

        @Override
        public boolean isShown() {
            return mIsShown;
        }

        public int getAndResetVerifierCallCount() {
            return mVerifierCallCount.getAndSet(0);
        }

        public int getAndResetAccessibilityVerifierCallCount() {
            return mAccessibilityVerifierCallCount.getAndSet(0);
        }

        @Override
        public String getKeyboardPackageName() {
            return mKeyboardPackageName.get();
        }

        public void setKeyboardPackageName(String packageName) {
            mKeyboardPackageName.set(packageName);
        }

        @Override
        public String sanitizeTextForPaste(String s) {
            if (s.startsWith(JAVASCRIPT_SCHEME)) {
                s = s.substring(JAVASCRIPT_SCHEME.length());
            }
            return s;
        }
    }

    public AutocompleteEditTextTest() {
        if (DEBUG) ShadowLog.stream = System.out;
    }

    @Before
    public void setUp() {
        if (DEBUG) Log.i(TAG, "setUp started.");
        MockitoAnnotations.initMocks(this);
        mContext =
                new ContextThemeWrapper(
                        ContextUtils.getApplicationContext(), R.style.Theme_BrowserUI_DayNight);

        mHasEditableInputConnectionEndBatchEditBug =
                testEditableInputConnectionEndBatchEditBug(mContext);

        mVerifier = spy(new Verifier());
        mAutocomplete = new TestAutocompleteEditText(mContext, null);
        mFocusPlaceHolder = new LinearLayout(mContext);
        mFocusPlaceHolder.setFocusable(true);
        mFocusPlaceHolder.addView(mAutocomplete);
        assertNotNull(mAutocomplete);

        // Pretend that the view is shown in the activity hierarchy, which is for accessibility
        // testing.
        Activity activity = Robolectric.buildActivity(Activity.class).create().get();
        activity.setContentView(mFocusPlaceHolder);
        assertNotNull(mFocusPlaceHolder.getParent());
        mIsShown = true;
        assertTrue(mAutocomplete.isShown());

        // Enable accessibility.
        ShadowAccessibilityManager manager =
                Shadows.shadowOf(
                        (AccessibilityManager)
                                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE));
        manager.setEnabled(true);
        manager.setTouchExplorationEnabled(true);
        AccessibilityState.setIsPerformGesturesEnabledForTesting(true);
        AccessibilityState.setIsTouchExplorationEnabledForTesting(true);

        mInOrder = inOrder(mVerifier);
        assertTrue(mAutocomplete.requestFocus());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_FOCUSED, "", "", 2, -1, -1, -1, -1);
        assertNotNull(mAutocomplete.onCreateInputConnection(new EditorInfo()));
        mInputConnection = mAutocomplete.getInputConnection();
        assertNotNull(mInputConnection);
        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(0, 1);

        // Feeder should call this at the beginning.
        mAutocomplete.setIgnoreTextChangesForAutocomplete(false);

        if (DEBUG) Log.i(TAG, "setUp finished.");
    }

    private void assertTexts(String userText, String autocompleteText, String additionalText) {
        assertEquals(userText, mAutocomplete.getTextWithoutAutocomplete());
        assertEquals(userText + autocompleteText, mAutocomplete.getTextWithAutocomplete());
        assertEquals(autocompleteText.length(), mAutocomplete.getAutocompleteLength());
        assertEquals(!TextUtils.isEmpty(autocompleteText), mAutocomplete.hasAutocomplete());
        assertEquals(additionalText, mAutocomplete.getAdditionalText().orElse(""));
    }

    private void assertVerifierCallCounts(
            int nonAccessibilityCallCount, int accessibilityCallCount) {
        assertEquals(nonAccessibilityCallCount, mAutocomplete.getAndResetVerifierCallCount());
        assertEquals(
                accessibilityCallCount, mAutocomplete.getAndResetAccessibilityVerifierCallCount());
    }

    private void verifyOnPopulateAccessibilityEvent(
            int eventType,
            String text,
            String beforeText,
            int itemCount,
            int fromIndex,
            int toIndex,
            int removedCount,
            int addedCount) {
        mInOrder.verify(mVerifier)
                .onPopulateAccessibilityEvent(
                        eventType,
                        text,
                        beforeText,
                        itemCount,
                        fromIndex,
                        toIndex,
                        removedCount,
                        addedCount);
    }

    @Test
    public void testAppend_CommitText() {
        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.empty());
        assertFalse(mAutocomplete.isCursorVisible());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "h", -1, 1, -1, 0, 10);
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "he".
        assertTrue(mInputConnection.commitText("e", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(2, 2);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                2,
                2,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "he", -1, 2, -1, 0, 9);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("he", "llo world", Optional.empty());
        assertFalse(mAutocomplete.isCursorVisible());

        mInOrder.verifyNoMoreInteractions();
        assertTexts("he", "llo world", "");
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "hello".
        assertTrue(mInputConnection.commitText("llo", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                5,
                5,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
        assertTexts("hello", " world", "");
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types a space inside a batch edit.
        assertTrue(mInputConnection.beginBatchEdit());
        // We should still show the intermediate autocomplete text to the user even in the middle of
        // a batch edit. Otherwise, the user may see flickering of autocomplete text.
        assertEquals("hello world", mAutocomplete.getText().toString());
        assertTrue(mInputConnection.commitText(" ", 1));
        assertEquals("hello world", mAutocomplete.getText().toString());
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertEquals("hello world", mAutocomplete.getText().toString());

        mInOrder.verifyNoMoreInteractions();
        assertLastBatchEdit(mInputConnection.endBatchEdit());

        // Autocomplete text gets redrawn.
        assertTexts("hello ", "world", "");
        assertTrue(mAutocomplete.shouldAutocomplete());
        mInOrder.verify(mVerifier).onUpdateSelection(6, 6);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                6,
                6,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world",
                "hello ",
                -1,
                6,
                -1,
                0,
                5);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mAutocomplete.setAutocompleteText("hello ", "world", Optional.of("foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("hello ", "world", "foo.com");
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
    }

    @Test
    public void testAppendWithAdditionalText_CommitText() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(true);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(1);

        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.of("www.foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.foo.com",
                "h",
                -1,
                1,
                -1,
                0,
                10);
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "he".
        assertTrue(mInputConnection.commitText("e", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(2, 2);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world - www.foo.com",
                "",
                25,
                2,
                2,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.foo.com",
                "he",
                -1,
                2,
                -1,
                0,
                9);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("he", "llo world", Optional.of("www.bar.com"));
        assertFalse(mAutocomplete.isCursorVisible());

        mInOrder.verifyNoMoreInteractions();
        assertTexts("he", "llo world", "www.bar.com");
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "hello".
        assertTrue(mInputConnection.commitText("llo", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world - www.bar.com",
                "",
                25,
                5,
                5,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.bar.com",
                "hello",
                -1,
                5,
                -1,
                0,
                6);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.of("www.foobar.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
        assertTexts("hello", " world", "www.foobar.com");
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types a space inside a batch edit.
        assertTrue(mInputConnection.beginBatchEdit());
        // We should still show the intermediate autocomplete text to the user even in the middle of
        // a batch edit. Otherwise, the user may see flickering of autocomplete text.
        assertEquals("hello world - www.foobar.com", mAutocomplete.getText().toString());
        assertTrue(mInputConnection.commitText(" ", 1));
        assertEquals("hello world - www.foobar.com", mAutocomplete.getText().toString());
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertEquals("hello world - www.foobar.com", mAutocomplete.getText().toString());

        mInOrder.verifyNoMoreInteractions();
        assertLastBatchEdit(mInputConnection.endBatchEdit());

        // Autocomplete text gets redrawn.
        assertTexts("hello ", "world", "www.foobar.com");
        assertTrue(mAutocomplete.shouldAutocomplete());
        mInOrder.verify(mVerifier).onUpdateSelection(6, 6);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world - www.foobar.com",
                "",
                28,
                6,
                6,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.foobar.com",
                "hello ",
                -1,
                6,
                -1,
                0,
                5);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mAutocomplete.setAutocompleteText("hello ", "world", Optional.of("www.foobar.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("hello ", "world", "www.foobar.com");
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
    }

    @Test
    public void testAdditionalTextColor() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(true);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(1);

        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.of("www.foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.foo.com",
                "h",
                -1,
                1,
                -1,
                0,
                10);
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        Editable editable = mAutocomplete.getEditableText();
        ForegroundColorSpan[] spans =
                editable.getSpans(0, editable.length(), ForegroundColorSpan.class);
        assertEquals(1, spans.length);
        assertEquals(
                SemanticColorUtils.getDefaultTextColorSecondary(mContext),
                spans[0].getForegroundColor());
    }

    @Test
    public void testAppendWithAdditionalText_noFullUrl() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(false);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(1);

        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.of("www.foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        // "www.foo.com" is not shown since the show full URL parameter set to false.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "h", -1, 1, -1, 0, 10);
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
    }

    @Test
    public void testAppendWithAdditionalText_minimumCharacters() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(true);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(4);

        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.of("www.foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        // The input characters are not enough, so additional texts are not shown.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "h", -1, 1, -1, 0, 10);
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "he".
        assertTrue(mInputConnection.commitText("e", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(2, 2);
        // The input characters are not enough, so additional texts are not shown.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                2,
                2,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "he", -1, 2, -1, 0, 9);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("he", "llo world", Optional.of("www.bar.com"));
        assertFalse(mAutocomplete.isCursorVisible());

        mInOrder.verifyNoMoreInteractions();
        assertTexts("he", "llo world", "www.bar.com");
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "hello".
        assertTrue(mInputConnection.commitText("llo", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        // The input characters are enough, so additional texts are shown.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world - www.bar.com",
                "",
                25,
                5,
                5,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.bar.com",
                "hello",
                -1,
                5,
                -1,
                0,
                6);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.of("www.foobar.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
        assertTexts("hello", " world", "www.foobar.com");
        assertTrue(mAutocomplete.shouldAutocomplete());
    }

    @Test
    public void testAppendWithAdditionalText_onSelectionChanged() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(true);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(1);

        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);

        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.of("www.foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.foo.com",
                "h",
                -1,
                1,
                -1,
                0,
                10);
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User taps on "hello world - www.fo[|]o.com".
        mAutocomplete.onSelectionChanged(20, 20);
        mInOrder.verify(mVerifier).onUpdateSelection(11, 11);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                11,
                11,
                -1,
                -1);

        // User selects on "[hello world - www.fo]o.com".
        mAutocomplete.onSelectionChanged(0, 20);
        mInOrder.verify(mVerifier).onUpdateSelection(0, 11);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                0,
                11,
                -1,
                -1);
    }

    @Test
    public void testAppendWithAdditionalText_removeAutocompleteAndAddtionalText() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(true);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(1);

        // User types "hello".
        assertTrue(mInputConnection.commitText("hello", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "", -1, 0, -1, 0, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.of("www.foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world - www.foo.com",
                "hello",
                -1,
                5,
                -1,
                0,
                6);
        assertVerifierCallCounts(0, 1);
        assertTexts("hello", " world", "www.foo.com");
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User taps on "he[|]llo world - www.foo.com", the autocomplete and additional text will be
        // removed.
        mAutocomplete.onSelectionChanged(2, 2);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
    }

    @Test
    public void testAppend_SetComposingText() {
        // User types "h".
        assertTrue(mInputConnection.setComposingText("h", 1));

        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();

        // The old model does not allow autocompletion here.
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.empty());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "h", -1, 1, -1, 0, 10);
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("h", "ello world", "");
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();

        // User types "hello".
        assertTrue(mInputConnection.setComposingText("hello", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                5,
                5,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTexts("hello", " world", "");

        // The old model does not allow autocompletion here.
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("hello", " world", "");
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();

        // User types a space.
        assertTrue(mInputConnection.beginBatchEdit());
        // We should still show the intermediate autocomplete text to the user even in the
        // middle of a batch edit. Otherwise, the user may see flickering of autocomplete text.
        assertEquals("hello world", mAutocomplete.getText().toString());

        assertTrue(mInputConnection.finishComposingText());

        assertEquals("hello world", mAutocomplete.getText().toString());

        assertTrue(mInputConnection.commitText(" ", 1));

        assertEquals("hello world", mAutocomplete.getText().toString());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();

        assertLastBatchEdit(mInputConnection.endBatchEdit());

        mInOrder.verify(mVerifier).onUpdateSelection(6, 6);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                6,
                6,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
                "hello world",
                "hello ",
                -1,
                6,
                -1,
                0,
                5);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();

        // Autocomplete text has been drawn at endBatchEdit().
        assertTexts("hello ", "world", "");

        // The old model can also autocomplete now.
        assertTrue(mAutocomplete.shouldAutocomplete());
        mAutocomplete.setAutocompleteText("hello ", "world", Optional.of("foo.com"));
        assertTexts("hello ", "world", "foo.com");
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
    }

    @Test
    public void testAppend_DispatchKeyEvent() {
        // User types "h".
        mAutocomplete.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_H));
        mAutocomplete.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_H));
        mInOrder.verify(mVerifier).onUpdateSelection(1, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "h", "", -1, 0, -1, 0, 1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "h", "", 1, 1, 1, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // The controller kicks in.
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.empty());
        // The non-spannable model changes selection in two steps.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "h", -1, 1, -1, 0, 10);
        assertVerifierCallCounts(0, 1);
        assertFalse(mAutocomplete.isCursorVisible());
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());

        // User types "he".
        mAutocomplete.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_E));
        mAutocomplete.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_E));
        mInOrder.verify(mVerifier).onUpdateSelection(2, 2);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                2,
                2,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "he", -1, 2, -1, 0, 9);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        // The new model tries to reuse autocomplete text.
        assertTexts("he", "llo world", "");
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("he", "llo world", Optional.of("foo.com"));
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
        assertTexts("he", "llo world", "foo.com");
        assertTrue(mAutocomplete.shouldAutocomplete());
        mInOrder.verifyNoMoreInteractions();
    }

    @Test
    public void testDelete_CommitText() {
        // User types "hello".
        assertTrue(mInputConnection.commitText("hello", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "", -1, 0, -1, 0, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        assertVerifierCallCounts(0, 1);
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("hello", " world", "");
        mInOrder.verifyNoMoreInteractions();

        // User deletes autocomplete.
        assertTrue(mInputConnection.deleteSurroundingText(1, 0)); // deletes one character

        // Pretend that we have deleted 'o' first.
        mInOrder.verify(mVerifier).onUpdateSelection(4, 4);
        // We restore 'o', and clear autocomplete text instead.
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        assertTrue(mAutocomplete.isCursorVisible());
        // Autocomplete removed.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "hello world", -1, 5, -1, 6, 0);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(3, 1);
        mInOrder.verifyNoMoreInteractions();
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello", "", "");

        // Keyboard app checks the current state.
        assertEquals("hello", mInputConnection.getTextBeforeCursor(10, 0));
        assertTrue(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello", "", "");
    }

    private boolean isComposing() {
        return BaseInputConnection.getComposingSpanStart(mAutocomplete.getText())
                != BaseInputConnection.getComposingSpanEnd(mAutocomplete.getText());
    }

    @Test
    public void testDelete_SetComposingText() {
        // User types "hello".
        assertTrue(mInputConnection.setComposingText("hello", 1));
        assertTrue(isComposing());
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "", -1, 0, -1, 0, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("hello", " world", "");
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();

        // User deletes autocomplete.
        assertTrue(mInputConnection.setComposingText("hell", 1));
        // Pretend that we have deleted 'o'.
        mInOrder.verify(mVerifier).onUpdateSelection(4, 4);
        // We restore 'o', finish composition, and clear autocomplete text instead.
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        assertTrue(mAutocomplete.isCursorVisible());
        assertFalse(isComposing());
        // Remove autocomplete.
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "hello world", -1, 5, -1, 6, 0);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(3, 1);
        mInOrder.verifyNoMoreInteractions();
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello", "", "");

        // Keyboard app checks the current state.
        assertEquals("hello", mInputConnection.getTextBeforeCursor(10, 0));
        assertTrue(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 0);
        mInOrder.verifyNoMoreInteractions();
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello", "", "");
    }

    @Test
    public void testDelete_SamsungKeyboard() {
        mAutocomplete.setKeyboardPackageName("com.sec.android.inputmethod");
        // User types "hello".
        assertTrue(mInputConnection.setComposingText("hello", 1));
        assertTrue(isComposing());
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        assertTexts("hello", " world", "");

        // User deletes autocomplete.
        assertTrue(mInputConnection.setComposingText("hell", 1));
        // Remove autocomplete.
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello", "", "");
        // Make sure that we do not finish composing text for Samsung keyboard - it does not update
        // its internal states when we ask this. (crbug.com/766888).
        assertTrue(isComposing());
    }

    @Test
    public void testDelete_SetComposingTextInBatchEdit() {
        // User types "hello".
        assertTrue(mInputConnection.setComposingText("hello", 1));

        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "", -1, 0, -1, 0, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(2, 2);
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        assertFalse(mAutocomplete.isCursorVisible());
        assertTexts("hello", " world", "");
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(0, 1);

        // User deletes 'o' in a batch edit.
        assertTrue(mInputConnection.beginBatchEdit());
        assertTrue(mInputConnection.setComposingText("hell", 1));

        // We restore 'o', and clear autocomplete text instead.
        assertTrue(mAutocomplete.isCursorVisible());
        assertFalse(mAutocomplete.shouldAutocomplete());

        // The user will see "hello" even in the middle of a batch edit.
        assertEquals("hello", mAutocomplete.getText().toString());

        // Keyboard app checks the current state.
        assertEquals("hell", mInputConnection.getTextBeforeCursor(10, 0));
        assertTrue(mAutocomplete.isCursorVisible());
        assertFalse(mAutocomplete.shouldAutocomplete());

        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(0, 0);

        assertLastBatchEdit(mInputConnection.endBatchEdit());
        mInOrder.verify(mVerifier).onUpdateSelection(4, 4);
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "hello world", -1, 5, -1, 6, 0);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello", "", "");
        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(3, 1);
    }

    @Test
    public void testSelect_SelectAutocomplete() {
        // User types "hello".
        assertTrue(mInputConnection.commitText("hello", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "", -1, 0, -1, 0, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        assertTexts("hello", " world", "");
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        // User touches autocomplete text.
        mAutocomplete.setSelection(7);
        mInOrder.verify(mVerifier).onUpdateSelection(7, 7);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                "hello world",
                "",
                11,
                7,
                7,
                -1,
                -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 1);
        mInOrder.verifyNoMoreInteractions();
        assertFalse(mAutocomplete.shouldAutocomplete());
        assertTexts("hello world", "", "");
    }

    @Test
    public void testSelect_SelectUserText() {
        // User types "hello".
        assertTrue(mInputConnection.commitText("hello", 1));
        mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "", -1, 0, -1, 0, 5);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 5, 5, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertTrue(mAutocomplete.shouldAutocomplete());
        // The controller kicks in.
        mAutocomplete.setAutocompleteText("hello", " world", Optional.empty());
        assertTexts("hello", " world", "");
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello world", "hello", -1, 5, -1, 0, 6);
        assertFalse(mAutocomplete.isCursorVisible());
        assertVerifierCallCounts(0, 1);
        mInOrder.verifyNoMoreInteractions();
        // User touches the user text.
        mAutocomplete.setSelection(3);
        assertTrue(mAutocomplete.isCursorVisible());
        mInOrder.verify(mVerifier).onUpdateSelection(3, 3);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, "hello", "hello world", -1, 5, -1, 6, 0);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, "hello", "", 5, 3, 3, -1, -1);
        mInOrder.verify(mVerifier).onAutocompleteTextStateChanged(false);
        assertVerifierCallCounts(2, 2);
        mInOrder.verifyNoMoreInteractions();
        assertFalse(mAutocomplete.shouldAutocomplete());
        // Autocomplete text is removed.
        assertTexts("hello", "", "");
    }

    @Test
    public void testAppend_AfterSelectAll() {
        final String url = "https://www.google.com/";
        mAutocomplete.setText(url);
        mAutocomplete.setSelection(0, url.length());
        assertTrue(mAutocomplete.isCursorVisible());
        // User types "h" - note that this is also starting character of the URL. The selection gets
        // replaced by what user types.
        assertTrue(mInputConnection.commitText("h", 1));
        // We want to allow inline autocomplete when the user overrides an existing URL.
        assertTrue(mAutocomplete.shouldAutocomplete());
        assertTrue(mAutocomplete.isCursorVisible());
    }

    @Test
    public void testIgnoreAndGet() {
        final String url = "https://www.google.com/";
        mAutocomplete.setIgnoreTextChangesForAutocomplete(true);
        mAutocomplete.setText(url);
        mAutocomplete.setIgnoreTextChangesForAutocomplete(false);
        mInputConnection.getTextBeforeCursor(1, 1);
        assertTrue(mAutocomplete.isCursorVisible());
        mInOrder.verifyNoMoreInteractions();
    }

    // crbug.com/760013
    @Test
    public void testOnSaveInstanceStateDoesNotCrash() {
        mInputConnection.setComposingText("h", 1);
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.of("foo.com"));
        // On Android JB, TextView#onSaveInstanceState() calls new SpannableString(mText). This
        // should not crash.
        new SpannableString(mAutocomplete.getText());
    }

    // crbug.com/759876
    @Test
    public void testFocusInAndSelectAll() {
        final String url = "https://google.com";
        final int len = url.length();
        mAutocomplete.setIgnoreTextChangesForAutocomplete(true);
        mAutocomplete.setText(url);
        mAutocomplete.setIgnoreTextChangesForAutocomplete(false);

        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(0, 0);

        assertTrue(mFocusPlaceHolder.requestFocus());

        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(0, 0);

        // LocationBarLayout does this.
        mAutocomplete.setSelectAllOnFocus(true);

        assertTrue(mAutocomplete.requestFocus());

        mInOrder.verify(mVerifier).onUpdateSelection(len, len);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, url, "", 18, 18, 18, -1, -1);
        mInOrder.verify(mVerifier).onUpdateSelection(0, len);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, url, "", 18, 0, 18, -1, -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_FOCUSED, url, "", 2, -1, -1, -1, -1);

        assertVerifierCallCounts(2, 3);
        mInOrder.verifyNoMoreInteractions();
    }

    // crbug.com/764749
    @Test
    public void testNonMatchingBatchEdit() {
        // beginBatchEdit() was not matched by endBatchEdit(), for some reason.
        mInputConnection.beginBatchEdit();

        // Restart input should reset batch edit count.
        assertNotNull(mAutocomplete.onCreateInputConnection(new EditorInfo()));
        mInputConnection = mAutocomplete.getInputConnection();

        assertTrue(mInputConnection.commitText("a", 1));
        // Works again.
        assertTrue(mAutocomplete.shouldAutocomplete());
    }

    // crbug.com/768323
    @Test
    public void testFocusLossHidesCursor() {
        assertTrue(mAutocomplete.isFocused());
        assertTrue(mAutocomplete.isCursorVisible());

        // AutocompleteEditText loses focus, and this hides cursor.
        assertTrue(mFocusPlaceHolder.requestFocus());

        assertFalse(mAutocomplete.isFocused());
        assertFalse(mAutocomplete.isCursorVisible());

        // Some IME operations may arrive after focus loss, but this should never show cursor.
        mInputConnection.getTextBeforeCursor(1, 0);
        assertFalse(mAutocomplete.isCursorVisible());
    }

    @Test
    public void testUnsupportedKeyboard() {
        mAutocomplete.setKeyboardPackageName("jp.co.sharp.android.iwnn");
        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        assertFalse(mAutocomplete.shouldAutocomplete());
    }

    // crbug.com/783165
    @Test
    public void testSetTextAndSelect() {
        // User types "h".
        assertTrue(mInputConnection.commitText("h", 1));
        assertTrue(mAutocomplete.shouldAutocomplete());
        mAutocomplete.setAutocompleteText("h", "ello world", Optional.empty());
        mAutocomplete.setIgnoreTextChangesForAutocomplete(true);
        mAutocomplete.setText("abcde");
        mAutocomplete.setIgnoreTextChangesForAutocomplete(false);
        assertEquals("abcde", mAutocomplete.getText().toString());

        mAutocomplete.setSelection(0);

        // Check the internal states are correct.
        assertEquals("abcde", mAutocomplete.getText().toString());
        assertEquals("abcde", mAutocomplete.getTextWithAutocomplete());
        assertEquals("abcde", mAutocomplete.getTextWithoutAutocomplete());
    }

    // crbug.com/810704
    @Test
    public void testPerformEditorAction() {
        // User types "goo".
        assertTrue(mInputConnection.setComposingText("goo", 1));
        assertTrue(mAutocomplete.shouldAutocomplete());
        mAutocomplete.setAutocompleteText("goo", "gle.com", Optional.empty());
        assertEquals("google.com", mAutocomplete.getText().toString());

        // User presses 'GO' key on the keyboard.
        assertTrue(mInputConnection.commitText("goo", 1));
        assertEquals("google.com", mAutocomplete.getText().toString());

        assertTrue(mInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO));
        assertEquals("google.com", mAutocomplete.getText().toString());
    }

    @Test
    public void testPerformEditorAction_withAdditionText() {
        OmniboxFeatures.sRichInlineAutocomplete.setForTesting(true);
        OmniboxFeatures.sRichInlineShowFullUrl.setForTesting(true);
        OmniboxFeatures.sRichInlineMinimumInputChars.setForTesting(3);

        // User types "goo".
        assertTrue(mInputConnection.setComposingText("goo", 1));
        assertTrue(mAutocomplete.shouldAutocomplete());
        mAutocomplete.setAutocompleteText("goo", "gle.com", Optional.of("www.google.com"));
        assertEquals("google.com - www.google.com", mAutocomplete.getText().toString());

        // User presses 'GO' key on the keyboard.
        assertTrue(mInputConnection.commitText("goo", 1));
        assertEquals("google.com - www.google.com", mAutocomplete.getText().toString());

        assertTrue(mInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO));
        assertEquals("google.com", mAutocomplete.getText().toString());
    }

    // crbug.com/810704
    @Test
    public void testPerformEditorActionInBatchEdit() {
        // User types "goo".
        assertTrue(mInputConnection.setComposingText("goo", 1));
        assertTrue(mAutocomplete.shouldAutocomplete());
        mAutocomplete.setAutocompleteText("goo", "gle.com", Optional.empty());
        assertEquals("google.com", mAutocomplete.getText().toString());

        // User presses 'GO' key on the keyboard.
        mInputConnection.beginBatchEdit();

        assertTrue(mInputConnection.commitText("goo", 1));
        assertEquals("google.com", mAutocomplete.getText().toString());

        assertTrue(mInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO));
        assertEquals("google.com", mAutocomplete.getText().toString());

        mInputConnection.endBatchEdit();

        assertEquals("google.com", mAutocomplete.getText().toString());
    }

    // crbug.com/759876
    @Test
    public void testTextSelectionGetsAnnouncedAgainOnFocus() {
        final String text = "hello";
        final int len = text.length();

        assertTrue(mInputConnection.commitText(text, len));
        mAutocomplete.setSelection(0, len);

        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, text, "", -1, 0, -1, 0, len);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
                text,
                "",
                len,
                len,
                len,
                -1,
                -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, text, "", len, 0, len, -1, -1);
        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(3, 3);

        assertTrue(mFocusPlaceHolder.requestFocus());
        mInOrder.verifyNoMoreInteractions();
        assertVerifierCallCounts(0, 0);

        // We left EditText with selected content. We should get the same event sent again now.
        mAutocomplete.setSelectAllOnFocus(true);
        assertTrue(mAutocomplete.requestFocus());

        mInOrder.verify(mVerifier).onUpdateSelection(0, len);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, text, "", len, 0, len, -1, -1);
        verifyOnPopulateAccessibilityEvent(
                AccessibilityEvent.TYPE_VIEW_FOCUSED, text, "", 2, -1, -1, -1, -1);

        assertVerifierCallCounts(2, 3);
        mInOrder.verifyNoMoreInteractions();
    }

    // crbug.com/759876
    @Test
    public void testEndBatchEditCanReturnFalse() {
        assertTrue(mInputConnection.beginBatchEdit());
        assertLastBatchEdit(mInputConnection.endBatchEdit());
        // Additional endBatchEdit() must continue returning false.
        assertFalse(mInputConnection.endBatchEdit());
    }

    @Test
    public void testJavascriptSchemeShouldBeRemovedWhenPaste() {
        assertTrue(mInputConnection.commitText("javascript:alert(\"Test\")", 1));
        assertEquals("alert(\"Test\")", mAutocomplete.getText().toString());
    }

    @Test
    public void testJavascriptSchemeShouldNotBeRemovedWhenPatialPaste() {
        assertTrue(mInputConnection.commitText("j", 1));
        assertTrue(mInputConnection.commitText("avascript:alert(\"Test\")", 1));
        assertEquals("javascript:alert(\"Test\")", mAutocomplete.getText().toString());
    }

    @Test
    public void testJavascriptSchemeShouldNotBeRemovedWhenPatialPaste_LastInputIsOneLetter() {
        assertTrue(mInputConnection.commitText("javascript", 1));
        assertTrue(mInputConnection.commitText(":", 1));
        assertEquals("javascript:", mAutocomplete.getText().toString());
    }
}