/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.core.widget;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.v4.testutils.LayoutDirectionActions.setLayoutDirection;
import static android.support.v4.testutils.TextViewActions.setCompoundDrawablesRelative;
import static android.support.v4.testutils.TextViewActions
        .setCompoundDrawablesRelativeWithIntrinsicBounds;
import static android.support.v4.testutils.TextViewActions.setMaxLines;
import static android.support.v4.testutils.TextViewActions.setMinLines;
import static android.support.v4.testutils.TextViewActions.setText;
import static android.support.v4.testutils.TextViewActions.setTextAppearance;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Looper;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.v4.BaseInstrumentationTestCase;
import android.support.v4.testutils.TestUtils;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.ColorInt;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.core.test.R;
import androidx.core.view.ViewCompat;

import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

@SmallTest
public class TextViewCompatTest extends BaseInstrumentationTestCase<TextViewTestActivity> {
    private static final String TAG = "TextViewCompatTest";

    private TextView mTextView;

    private class TestDrawable extends ColorDrawable {
        private int mWidth;
        private int mHeight;

        TestDrawable(@ColorInt int color, int width, int height) {
            super(color);
            mWidth = width;
            mHeight = height;
        }

        @Override
        public int getIntrinsicWidth() {
            return mWidth;
        }

        @Override
        public int getIntrinsicHeight() {
            return mHeight;
        }
    }

    public TextViewCompatTest() {
        super(TextViewTestActivity.class);
    }

    @Before
    public void setUp() {
        mTextView = (TextView) mActivityTestRule.getActivity().findViewById(R.id.text_view);
    }

    @Test
    public void testMaxLines() throws Throwable {
        final int maxLinesCount = 4;
        onView(withId(R.id.text_view)).perform(setMaxLines(maxLinesCount));

        assertEquals("Empty view: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                maxLinesCount);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_short));
        assertEquals("Short text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                maxLinesCount);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
        assertEquals("Medium text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                maxLinesCount);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
        assertEquals("Long text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                maxLinesCount);
    }

    @Test
    public void testMinLines() throws Throwable {
        final int minLinesCount = 3;
        onView(withId(R.id.text_view)).perform(setMinLines(minLinesCount));

        assertEquals("Empty view: Min lines must match", TextViewCompat.getMinLines(mTextView),
                minLinesCount);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_short));
        assertEquals("Short text: Min lines must match", TextViewCompat.getMinLines(mTextView),
                minLinesCount);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
        assertEquals("Medium text: Min lines must match", TextViewCompat.getMinLines(mTextView),
                minLinesCount);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
        assertEquals("Long text: Min lines must match", TextViewCompat.getMinLines(mTextView),
                minLinesCount);
    }

    @Test
    public void testStyle() throws Throwable {
        onView(withId(R.id.text_view)).perform(setTextAppearance(R.style.TextMediumStyle));

        final Resources res = mActivityTestRule.getActivity().getResources();
        assertTrue("Styled text view: style",
                mTextView.getTypeface().isItalic() || (mTextView.getPaint().getTextSkewX() < 0));
        assertEquals("Styled text view: color", mTextView.getTextColors().getDefaultColor(),
                res.getColor(R.color.text_color));
        assertEquals("Styled text view: size", mTextView.getTextSize(),
                (float) res.getDimensionPixelSize(R.dimen.text_medium_size), 1.0f);
    }

    @Test
    public void testCompoundDrawablesRelative() throws Throwable {
        final Drawable drawableStart = new ColorDrawable(0xFFFF0000);
        drawableStart.setBounds(0, 0, 20, 20);
        final Drawable drawableTop = new ColorDrawable(0xFF00FF00);
        drawableTop.setBounds(0, 0, 30, 25);
        final Drawable drawableEnd = new ColorDrawable(0xFF0000FF);
        drawableEnd.setBounds(0, 0, 25, 20);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
                drawableTop, drawableEnd, null));

        final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();

        assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableStart);
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[0].getBounds().width(), 20);
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[0].getBounds().height(), 20);

        assertEquals("Compound drawable: top", drawablesAbsolute[1], drawableTop);
        assertEquals("Compound drawable: top width",
                drawablesAbsolute[1].getBounds().width(), 30);
        assertEquals("Compound drawable: top height",
                drawablesAbsolute[1].getBounds().height(), 25);

        assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableEnd);
        assertEquals("Compound drawable: right width",
                drawablesAbsolute[2].getBounds().width(), 25);
        assertEquals("Compound drawable: right height",
                drawablesAbsolute[2].getBounds().height(), 20);

        assertNull("Compound drawable: bottom", drawablesAbsolute[3]);
    }

    @Test
    public void testCompoundDrawablesRelativeRtl() throws Throwable {
        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));

        final Drawable drawableStart = new ColorDrawable(0xFFFF0000);
        drawableStart.setBounds(0, 0, 20, 20);
        final Drawable drawableTop = new ColorDrawable(0xFF00FF00);
        drawableTop.setBounds(0, 0, 30, 25);
        final Drawable drawableEnd = new ColorDrawable(0xFF0000FF);
        drawableEnd.setBounds(0, 0, 25, 20);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
                drawableTop, drawableEnd, null));

        // Check to see whether our text view is under RTL mode
        if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
            // This will happen on v17- devices
            return;
        }

        final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();

        // End drawable should be returned as left
        assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableEnd);
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[0].getBounds().width(), 25);
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[0].getBounds().height(), 20);

        assertEquals("Compound drawable: top", drawablesAbsolute[1], drawableTop);
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[1].getBounds().width(), 30);
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[1].getBounds().height(), 25);

        // Start drawable should be returned as right
        assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableStart);
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[2].getBounds().width(), 20);
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[2].getBounds().height(), 20);

        assertNull("Compound drawable: bottom", drawablesAbsolute[3]);
    }

    @Test
    public void testCompoundDrawablesRelativeWithIntrinsicBounds() throws Throwable {
        final Drawable drawableStart = new TestDrawable(0xFFFF0000, 30, 20);
        final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 25, 45);
        final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 15, 35);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
                drawableStart, null, drawableEnd, drawableBottom));

        final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();

        assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableStart);
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[0].getBounds().width(), 30);
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[0].getBounds().height(), 20);

        assertNull("Compound drawable: top", drawablesAbsolute[1]);

        assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableEnd);
        assertEquals("Compound drawable: right width",
                drawablesAbsolute[2].getBounds().width(), 25);
        assertEquals("Compound drawable: right height",
                drawablesAbsolute[2].getBounds().height(), 45);

        assertEquals("Compound drawable: bottom", drawablesAbsolute[3], drawableBottom);
        assertEquals("Compound drawable: bottom width",
                drawablesAbsolute[3].getBounds().width(), 15);
        assertEquals("Compound drawable: bottom height",
                drawablesAbsolute[3].getBounds().height(), 35);
    }

    @Test
    public void testCompoundDrawablesRelativeWithIntrinsicBoundsRtl() throws Throwable {
        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));

        final Drawable drawableStart = new TestDrawable(0xFFFF0000, 30, 20);
        final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 25, 45);
        final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 15, 35);

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
                drawableStart, null, drawableEnd, drawableBottom));

        // Check to see whether our text view is under RTL mode
        if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
            // This will happen on v17- devices
            return;
        }

        final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();

        // End drawable should be returned as left
        assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableEnd);
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[0].getBounds().width(), 25);
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[0].getBounds().height(), 45);

        assertNull("Compound drawable: top", drawablesAbsolute[1]);

        // Start drawable should be returned as right
        assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableStart);
        assertEquals("Compound drawable: right width",
                drawablesAbsolute[2].getBounds().width(), 30);
        assertEquals("Compound drawable: right height",
                drawablesAbsolute[2].getBounds().height(), 20);

        assertEquals("Compound drawable: bottom", drawablesAbsolute[3], drawableBottom);
        assertEquals("Compound drawable: bottom width",
                drawablesAbsolute[3].getBounds().width(), 15);
        assertEquals("Compound drawable: bottom height",
                drawablesAbsolute[3].getBounds().height(), 35);
    }

    @Test
    public void testCompoundDrawablesRelativeWithIntrinsicBoundsById() throws Throwable {
        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
                R.drawable.test_drawable_red, 0,
                R.drawable.test_drawable_green, R.drawable.test_drawable_blue));

        final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
        final Resources res = mActivityTestRule.getActivity().getResources();

        // The entire left drawable should be the specific red color
        TestUtils.assertAllPixelsOfColor("Compound drawable: left color",
                drawablesAbsolute[0], res.getColor(R.color.test_red));
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[0].getBounds().width(),
                res.getDimensionPixelSize(R.dimen.drawable_small_size));
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[0].getBounds().height(),
                res.getDimensionPixelSize(R.dimen.drawable_medium_size));

        assertNull("Compound drawable: top", drawablesAbsolute[1]);

        // The entire right drawable should be the specific green color
        TestUtils.assertAllPixelsOfColor("Compound drawable: right color",
                drawablesAbsolute[2], res.getColor(R.color.test_green));
        assertEquals("Compound drawable: right width",
                drawablesAbsolute[2].getBounds().width(),
                res.getDimensionPixelSize(R.dimen.drawable_medium_size));
        assertEquals("Compound drawable: right height",
                drawablesAbsolute[2].getBounds().height(),
                res.getDimensionPixelSize(R.dimen.drawable_large_size));

        // The entire bottom drawable should be the specific blue color
        TestUtils.assertAllPixelsOfColor("Compound drawable: bottom color",
                drawablesAbsolute[3], res.getColor(R.color.test_blue));
        assertEquals("Compound drawable: bottom width",
                drawablesAbsolute[3].getBounds().width(),
                res.getDimensionPixelSize(R.dimen.drawable_large_size));
        assertEquals("Compound drawable: bottom height",
                drawablesAbsolute[3].getBounds().height(),
                res.getDimensionPixelSize(R.dimen.drawable_small_size));
    }

    @Test
    public void testCompoundDrawablesRelativeWithIntrinsicBoundsByIdRtl() throws Throwable {
        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));

        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
                R.drawable.test_drawable_red, 0,
                R.drawable.test_drawable_green, R.drawable.test_drawable_blue));

        // Check to see whether our text view is under RTL mode
        if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
            // This will happen on v17- devices
            return;
        }

        final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
        final Resources res = mActivityTestRule.getActivity().getResources();

        // The entire left / end drawable should be the specific green color
        TestUtils.assertAllPixelsOfColor("Compound drawable: left color",
                drawablesAbsolute[0], res.getColor(R.color.test_green));
        assertEquals("Compound drawable: left width",
                drawablesAbsolute[0].getBounds().width(),
                res.getDimensionPixelSize(R.dimen.drawable_medium_size));
        assertEquals("Compound drawable: left height",
                drawablesAbsolute[0].getBounds().height(),
                res.getDimensionPixelSize(R.dimen.drawable_large_size));

        assertNull("Compound drawable: top", drawablesAbsolute[1]);

        // The entire right drawable should be the specific red color
        TestUtils.assertAllPixelsOfColor("Compound drawable: right color",
                drawablesAbsolute[2], res.getColor(R.color.test_red));
        assertEquals("Compound drawable: right width",
                drawablesAbsolute[2].getBounds().width(),
                res.getDimensionPixelSize(R.dimen.drawable_small_size));
        assertEquals("Compound drawable: right height",
                drawablesAbsolute[2].getBounds().height(),
                res.getDimensionPixelSize(R.dimen.drawable_medium_size));

        // The entire bottom drawable should be the specific blue color
        TestUtils.assertAllPixelsOfColor("Compound drawable: bottom color",
                drawablesAbsolute[3], res.getColor(R.color.test_blue));
        assertEquals("Compound drawable: bottom width",
                drawablesAbsolute[3].getBounds().width(),
                res.getDimensionPixelSize(R.dimen.drawable_large_size));
        assertEquals("Compound drawable: bottom height",
                drawablesAbsolute[3].getBounds().height(),
                res.getDimensionPixelSize(R.dimen.drawable_small_size));
    }

    @Test
    public void testCompoundDrawablesRelativeGetterAndSetter() {
        final Drawable drawableStart = new TestDrawable(0xFFFF0000, 20, 20);
        final Drawable drawableTop = new TestDrawable(0xFFFFFF00, 20, 20);
        final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 20, 20);
        final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 20, 20);

        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
                drawableTop, drawableEnd, drawableBottom));

        // Check to see whether our text view is under RTL mode
        if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
            // This will happen on v17- devices
            return;
        }

        final Drawable[] drawablesRelative = TextViewCompat.getCompoundDrawablesRelative(mTextView);
        assertEquals(drawableStart, drawablesRelative[0]);
        assertEquals(drawableTop, drawablesRelative[1]);
        assertEquals(drawableEnd, drawablesRelative[2]);
        assertEquals(drawableBottom, drawablesRelative[3]);
    }

    @Test
    public void testSetCustomSelectionActionModeCallback_doesNotIgnoreTheGivenCallback() {
        // JB devices require the current thread to be prepared as a looper for this test.
        // The test causes the creation of an Editor object, which uses an UserDictionaryListener
        // that is handled on the main looper.
        Looper.prepare();

        final boolean[] callbackCalled = new boolean[4];
        TextViewCompat.setCustomSelectionActionModeCallback(mTextView, new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                callbackCalled[0] = true;
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                callbackCalled[1] = true;
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                callbackCalled[2] = true;
                return true;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {
                callbackCalled[3] = true;
            }
        });
        final Menu menu = new MenuBuilder(mTextView.getContext());
        final MenuItem item = menu.add("Option");
        mTextView.getCustomSelectionActionModeCallback().onCreateActionMode(null, menu);
        mTextView.getCustomSelectionActionModeCallback().onPrepareActionMode(null, menu);
        mTextView.getCustomSelectionActionModeCallback().onActionItemClicked(null, item);
        mTextView.getCustomSelectionActionModeCallback().onDestroyActionMode(null);
        for (boolean called : callbackCalled) {
            assertTrue(called);
        }
    }

    @Test
    @SdkSuppress(minSdkVersion = 26, maxSdkVersion =  27)
    public void testSetCustomSelectionActionModeCallback_fixesBugInO() {
        // Create mock context and package manager for the text view.
        final PackageManager packageManagerMock = spy(mTextView.getContext().getPackageManager());
        final Context contextMock = spy(mTextView.getContext());
        when(contextMock.getPackageManager()).thenReturn(packageManagerMock);
        final TextView tvMock = spy(mTextView);
        // Set the new context on textViewMock by reflection, as TextView#getContext() is final.
        try {
            final Field contextField = View.class.getDeclaredField("mContext");
            contextField.setAccessible(true);
            contextField.set(tvMock, contextMock);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            // We should be able to set mContext by reflection.
            assertTrue(false);
        }
        // Create fake activities able to handle the ACTION_PROCESS_TEXT intent.
        final ResolveInfo info1 = new ResolveInfo();
        info1.activityInfo = new ActivityInfo();
        info1.activityInfo.packageName = contextMock.getPackageName();
        info1.activityInfo.name = "Activity 1";
        info1.nonLocalizedLabel = "Option 3";
        final ResolveInfo info2 = new ResolveInfo();
        info2.activityInfo = new ActivityInfo();
        info2.activityInfo.packageName = contextMock.getPackageName();
        info2.activityInfo.name = "Activity 2";
        info2.nonLocalizedLabel = "Option 4";
        final ResolveInfo info3 = new ResolveInfo();
        info3.activityInfo = new ActivityInfo();
        info3.activityInfo.packageName = contextMock.getPackageName();
        info3.activityInfo.name = "Activity 3";
        info3.nonLocalizedLabel = "Option 5";
        final List<ResolveInfo> infos = Arrays.asList(info1, info2, info3);
        doAnswer(new Answer() {
            @Override
            public Object answer(final InvocationOnMock invocation) throws Throwable {
                final Intent intent = invocation.getArgument(0);
                if (Intent.ACTION_PROCESS_TEXT.equals(intent.getAction())) {
                    return infos;
                }
                return invocation.callRealMethod();
            }
        }).when(packageManagerMock).queryIntentActivities((Intent) any(), anyInt());
        // Set a no op callback on the mocked text view, which should fix the SDK26 bug.
        TextViewCompat.setCustomSelectionActionModeCallback(tvMock, new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                return true;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {

            }
        });
        // Create a fake menu with two non process text items and two process text items.
        final Menu menu = new MenuBuilder(tvMock.getContext());
        menu.add(Menu.NONE, Menu.NONE, 1, "Option 1");
        menu.add(Menu.NONE, Menu.NONE, 2, "Option 2");
        menu.add(Menu.NONE, Menu.NONE, 100, "Option 3")
                .setIntent(new Intent(Intent.ACTION_PROCESS_TEXT));
        menu.add(Menu.NONE, Menu.NONE, 101, "Option 5")
                .setIntent(new Intent(Intent.ACTION_PROCESS_TEXT));
        // Run the callback and verify that the menu was updated. Its size should have increased
        // with 1, as now there are 3 process text options instead of 2 to be displayed.
        tvMock.getCustomSelectionActionModeCallback().onPrepareActionMode(null, menu);
        assertEquals(5, menu.size());
        for (int i = 0; i < menu.size(); ++i) {
            assertEquals("Option " + (i + 1), menu.getItem(i).getTitle());
        }
    }

    @UiThreadTest
    @Test
    public void testSetFirstBaselineToTopHeight() {
        mTextView.setText("This is some random text");
        final int padding = 100;
        mTextView.setPadding(padding, padding, padding, padding);

        final Paint.FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
        final int fontMetricsTop = Math.max(
                Math.abs(fontMetrics.top), Math.abs(fontMetrics.ascent));

        int firstBaselineToTopHeight = fontMetricsTop + 10;
        TextViewCompat.setFirstBaselineToTopHeight(mTextView, firstBaselineToTopHeight);
        assertEquals(firstBaselineToTopHeight,
                TextViewCompat.getFirstBaselineToTopHeight(mTextView));
        assertNotEquals(padding, mTextView.getPaddingTop());

        firstBaselineToTopHeight = fontMetricsTop + 40;
        TextViewCompat.setFirstBaselineToTopHeight(mTextView, firstBaselineToTopHeight);
        assertEquals(firstBaselineToTopHeight,
                TextViewCompat.getFirstBaselineToTopHeight(mTextView));

        mTextView.setPadding(padding, padding, padding, padding);
        assertEquals(padding, mTextView.getPaddingTop());
    }

    @UiThreadTest
    @Test
    public void testSetFirstBaselineToTopHeight_tooSmall() {
        mTextView.setText("This is some random text");
        final int padding = 100;
        mTextView.setPadding(padding, padding, padding, padding);

        final Paint.FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
        final int fontMetricsTop = Math.min(
                Math.abs(fontMetrics.top), Math.abs(fontMetrics.ascent));

        int firstBaselineToTopHeight = fontMetricsTop - 1;
        TextViewCompat.setFirstBaselineToTopHeight(mTextView, firstBaselineToTopHeight);
        assertNotEquals(firstBaselineToTopHeight,
                TextViewCompat.getFirstBaselineToTopHeight(mTextView));
        assertEquals(padding, mTextView.getPaddingTop());
    }

    @UiThreadTest
    @Test(expected = IllegalArgumentException.class)
    public void testSetFirstBaselineToTopHeight_negative() {
        TextViewCompat.setFirstBaselineToTopHeight(mTextView, -1);
    }

    @UiThreadTest
    @Test
    public void testSetLastBaselineToBottomHeight() {
        mTextView.setText("This is some random text");
        final int padding = 100;
        mTextView.setPadding(padding, padding, padding, padding);

        final Paint.FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
        final int fontMetricsBottom = Math.max(
                Math.abs(fontMetrics.bottom), Math.abs(fontMetrics.descent));

        int lastBaselineToBottomHeight = fontMetricsBottom + 20;
        TextViewCompat.setLastBaselineToBottomHeight(mTextView, lastBaselineToBottomHeight);
        assertEquals(lastBaselineToBottomHeight,
                TextViewCompat.getLastBaselineToBottomHeight(mTextView));
        assertNotEquals(padding, mTextView.getPaddingBottom());

        lastBaselineToBottomHeight = fontMetricsBottom + 30;
        TextViewCompat.setLastBaselineToBottomHeight(mTextView, lastBaselineToBottomHeight);
        assertEquals(lastBaselineToBottomHeight,
                TextViewCompat.getLastBaselineToBottomHeight(mTextView));

        mTextView.setPadding(padding, padding, padding, padding);
        assertEquals(padding, mTextView.getPaddingBottom());
    }

    @UiThreadTest
    @Test
    public void testSetLastBaselineToBottomHeight_tooSmall() {
        mTextView.setText("This is some random text");
        final int padding = 100;
        mTextView.setPadding(padding, padding, padding, padding);

        final Paint.FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
        final int fontMetricsBottom = Math.min(
                Math.abs(fontMetrics.bottom), Math.abs(fontMetrics.descent));

        int lastBaselineToBottomHeight = fontMetricsBottom - 1;
        TextViewCompat.setLastBaselineToBottomHeight(mTextView, lastBaselineToBottomHeight);
        assertNotEquals(lastBaselineToBottomHeight,
                TextViewCompat.getLastBaselineToBottomHeight(mTextView));
        assertEquals(padding, mTextView.getPaddingBottom());
    }

    @UiThreadTest
    @Test(expected = IllegalArgumentException.class)
    public void testSetLastBaselineToBottomHeight_negative() {
        TextViewCompat.setLastBaselineToBottomHeight(mTextView, -1);
    }

    @UiThreadTest
    @Test
    public void testSetLineHeight() {
        mTextView.setText("This is some random text");
        final float lineSpacingExtra = 50;
        final float lineSpacingMultiplier = 0.2f;
        mTextView.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);

        TextViewCompat.setLineHeight(mTextView, 100);
        assertEquals(100, mTextView.getLineHeight());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            assertNotEquals(lineSpacingExtra, mTextView.getLineSpacingExtra(), 0);
            assertNotEquals(lineSpacingMultiplier, mTextView.getLineSpacingMultiplier(), 0);
        }

        TextViewCompat.setLineHeight(mTextView, 200);
        assertEquals(200, mTextView.getLineHeight());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mTextView.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);
            assertEquals(lineSpacingExtra, mTextView.getLineSpacingExtra(), 0);
            assertEquals(lineSpacingMultiplier, mTextView.getLineSpacingMultiplier(), 0);
        }
    }

    @UiThreadTest
    @Test(expected = IllegalArgumentException.class)
    public void testSetLineHeight_negative() {
        TextViewCompat.setLineHeight(mTextView, -1);
    }
}
