/*
 * Copyright (C) 2008 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 android.text.cts;

import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;
import dalvik.annotation.ToBeFixed;

import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Paint.FontMetricsInt;
import android.os.Parcel;
import android.os.Parcelable;
import android.test.AndroidTestCase;
import android.text.GetChars;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.EllipsizeCallback;
import android.text.TextUtils.TruncateAt;
import android.text.style.BackgroundColorSpan;
import android.text.style.ReplacementSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.URLSpan;
import android.util.StringBuilderPrinter;

import java.util.ArrayList;
import java.util.regex.Pattern;

/**
 * Test {@link TextUtils}.
 */
@TestTargetClass(TextUtils.class)
public class TextUtilsTest extends AndroidTestCase {
    private static String mEllipsis;
    private int mStart;
    private int mEnd;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mEllipsis = getEllipsis();
        resetRange();
    }

    private void resetRange() {
        mStart = -1;
        mEnd = -1;
    }

    /**
     * Get the ellipsis from system.
     * @return the string of ellipsis.
     */
    private String getEllipsis() {
        String text = "xxxxx";
        TextPaint p = new TextPaint();
        float width = p.measureText(text.substring(1));
        String re = TextUtils.ellipsize(text, p, width, TruncateAt.START).toString();
        return re.substring(0, re.indexOf("x"));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "commaEllipsize",
        args = {CharSequence.class, TextPaint.class, float.class, String.class, String.class}
    )
    @ToBeFixed(bug = "1688347 ", explanation = "The javadoc for commaEllipsize() " +
            "does not discuss any of the corner cases")
    public void testCommaEllipsize() {
        TextPaint p = new TextPaint();
        String text = "long, string, to, truncate";

        float textWidth = p.measureText("long, 3 plus");
        // avail is shorter than text width for only one item plus the appropriate ellipsis.
        // issue 1688347, the expected result for this case does not be described
        // in the javadoc of commaEllipsize().
        assertEquals("",
                TextUtils.commaEllipsize(text, p, textWidth - 1, "plus 1", "%d plus").toString());
        // avail is long enough for only one item plus the appropriate ellipsis.
        assertEquals("long, 3 plus",
                TextUtils.commaEllipsize(text, p, textWidth, "plus 1", "%d plus").toString());

        // avail is long enough for two item plus the appropriate ellipsis.
        textWidth = p.measureText("long, string, 2 more");
        assertEquals("long, string, 2 more",
                TextUtils.commaEllipsize(text, p, textWidth, "more 1", "%d more").toString());

        // avail is long enough for the whole sentence.
        textWidth = p.measureText("long, string, to, truncate");
        assertEquals("long, string, to, truncate",
                TextUtils.commaEllipsize(text, p, textWidth, "more 1", "%d more").toString());

        // the sentence is extended, avail is NOT long enough for the whole sentence.
        assertEquals("long, string, to, more 1", TextUtils.commaEllipsize(
                text + "-extended", p, textWidth, "more 1", "%d more").toString());

        // exceptional value
        assertEquals("", TextUtils.commaEllipsize(text, p, -1f, "plus 1", "%d plus").toString());

        assertEquals(text, TextUtils.commaEllipsize(
                text, p, Float.MAX_VALUE, "more 1", "%d more").toString());

        assertEquals("long, string, to, null", TextUtils.commaEllipsize(
                text + "-extended", p, textWidth, null, "%d more").toString());

        try {
            TextUtils.commaEllipsize(null, p, textWidth, "plus 1", "%d plus");
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // issue 1688347, not clear what is supposed to happen if the text to truncate is null.
        }

        try {
            TextUtils.commaEllipsize(text, null, textWidth, "plus 1", "%d plus");
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // issue 1688347, not clear what is supposed to happen if TextPaint is null.
        }

        try {
            TextUtils.commaEllipsize(text, p, textWidth, "plus 1", null);
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // issue 1688347, not clear what is supposed to happen
            // if the string for "%d more" in the current locale is null.
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "concat",
        args = {CharSequence[].class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for concat() is incomplete." +
            "1. doesn't explain @param and @return" +
            "2. doesn't describe the expected result when parameter is empty" +
            "3. doesn't discuss the case that parameter is expectional.")
    public void testConcat() {
        // issue 1695243
        // the javadoc for concat() doesn't describe the expected result when parameter is empty.
        assertEquals("", TextUtils.concat().toString());

        assertEquals("first", TextUtils.concat("first").toString());

        assertEquals("first, second", TextUtils.concat("first", ", ", "second").toString());

        SpannableString string1 = new SpannableString("first");
        SpannableString string2 = new SpannableString("second");
        final String url = "www.test_url.com";
        URLSpan urlSpan = new URLSpan(url);
        string1.setSpan(urlSpan, 0, string1.length() - 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN);
        string2.setSpan(bgColorSpan, 0, string2.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        final String comma = ", ";
        Spanned strResult = (Spanned) TextUtils.concat(string1, comma, string2);
        assertEquals(string1.toString() + comma + string2.toString(), strResult.toString());
        Object spans[] = strResult.getSpans(0, strResult.length(), Object.class);
        assertEquals(2, spans.length);
        assertTrue(spans[0] instanceof URLSpan);
        assertEquals(url, ((URLSpan) spans[0]).getURL());
        assertTrue(spans[1] instanceof BackgroundColorSpan);
        assertEquals(Color.GREEN, ((BackgroundColorSpan) spans[1]).getBackgroundColor());
        assertEquals(0, strResult.getSpanStart(urlSpan));
        assertEquals(string1.length() - 1, strResult.getSpanEnd(urlSpan));
        assertEquals(string1.length() + comma.length(), strResult.getSpanStart(bgColorSpan));
        assertEquals(strResult.length() - 1, strResult.getSpanEnd(bgColorSpan));

        assertEquals(string1, TextUtils.concat(string1));

        // issue 1695243, the javadoc for concat() doesn't describe
        // the expected result when parameters are null.
        assertEquals(null, TextUtils.concat((CharSequence) null));

        try {
            TextUtils.concat((CharSequence[]) null);
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "copySpansFrom",
        args = {Spanned.class, int.class, int.class, Class.class, Spannable.class, int.class}
    )
    @ToBeFixed(bug = "1688347", explanation = "the javadoc for copySpansFrom() does not exist.")
    public void testCopySpansFrom() {
        Object[] spans;
        String text = "content";
        SpannableString source1 = new SpannableString(text);
        int midPos = source1.length() / 2;
        final String url = "www.test_url.com";
        URLSpan urlSpan = new URLSpan(url);
        source1.setSpan(urlSpan, 0, midPos, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN);
        source1.setSpan(bgColorSpan, midPos - 1,
                source1.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        // normal test
        SpannableString dest1 = new SpannableString(text);
        TextUtils.copySpansFrom(source1, 0, source1.length(), Object.class, dest1, 0);
        spans = dest1.getSpans(0, dest1.length(), Object.class);
        assertEquals(2, spans.length);
        assertTrue(spans[0] instanceof URLSpan);
        assertEquals(url, ((URLSpan) spans[0]).getURL());
        assertTrue(spans[1] instanceof BackgroundColorSpan);
        assertEquals(Color.GREEN, ((BackgroundColorSpan) spans[1]).getBackgroundColor());
        assertEquals(0, dest1.getSpanStart(urlSpan));
        assertEquals(midPos, dest1.getSpanEnd(urlSpan));
        assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, dest1.getSpanFlags(urlSpan));
        assertEquals(midPos - 1, dest1.getSpanStart(bgColorSpan));
        assertEquals(source1.length() - 1, dest1.getSpanEnd(bgColorSpan));
        assertEquals(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, dest1.getSpanFlags(bgColorSpan));

        SpannableString source2 = new SpannableString(text);
        source2.setSpan(urlSpan, 0, source2.length() - 1, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        SpannableString dest2 = new SpannableString(text);
        TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest2, 0);
        spans = dest2.getSpans(0, dest2.length(), Object.class);
        assertEquals(1, spans.length);
        assertTrue(spans[0] instanceof URLSpan);
        assertEquals(url, ((URLSpan) spans[0]).getURL());
        assertEquals(0, dest2.getSpanStart(urlSpan));
        assertEquals(source2.length() - 1, dest2.getSpanEnd(urlSpan));
        assertEquals(Spanned.SPAN_EXCLUSIVE_INCLUSIVE, dest2.getSpanFlags(urlSpan));

        SpannableString dest3 = new SpannableString(text);
        TextUtils.copySpansFrom(source2, 0, source2.length(), BackgroundColorSpan.class, dest3, 0);
        spans = dest3.getSpans(0, dest3.length(), Object.class);
        assertEquals(0, spans.length);
        TextUtils.copySpansFrom(source2, 0, source2.length(), URLSpan.class, dest3, 0);
        spans = dest3.getSpans(0, dest3.length(), Object.class);
        assertEquals(1, spans.length);

        SpannableString dest4 = new SpannableString("short");
        try {
            TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest4, 0);
            fail("Should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }
        TextUtils.copySpansFrom(source2, 0, dest4.length(), Object.class, dest4, 0);
        spans = dest4.getSpans(0, dest4.length(), Object.class);
        assertEquals(1, spans.length);
        assertEquals(0, dest4.getSpanStart(spans[0]));
        // issue 1688347, not clear the expected result when 'start ~ end' only
        // covered a part of the span.
        assertEquals(dest4.length(), dest4.getSpanEnd(spans[0]));

        SpannableString dest5 = new SpannableString("longer content");
        TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest5, 0);
        spans = dest5.getSpans(0, 1, Object.class);
        assertEquals(1, spans.length);

        dest5 = new SpannableString("longer content");
        TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest5, 2);
        spans = dest5.getSpans(0, 1, Object.class);
        assertEquals(0, spans.length);
        spans = dest5.getSpans(2, dest5.length(), Object.class);
        assertEquals(1, spans.length);
        try {
            TextUtils.copySpansFrom(source2, 0, source2.length(),
                    Object.class, dest5, dest5.length() - source2.length() + 2);
            fail("Should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        // issue 1688347, no javadoc about the expected behavior of the exceptional argument.
        // exceptional source start
        SpannableString dest6 = new SpannableString("exceptional test");
        TextUtils.copySpansFrom(source2, -1, source2.length(), Object.class, dest6, 0);
        spans = dest6.getSpans(0, dest6.length(), Object.class);
        assertEquals(1, spans.length);
        dest6 = new SpannableString("exceptional test");
        TextUtils.copySpansFrom(source2, Integer.MAX_VALUE, source2.length() - 1,
                    Object.class, dest6, 0);
        spans = dest6.getSpans(0, dest6.length(), Object.class);
        assertEquals(0, spans.length);

        // exceptional source end
        dest6 = new SpannableString("exceptional test");
        TextUtils.copySpansFrom(source2, 0, -1, Object.class, dest6, 0);
        spans = dest6.getSpans(0, dest6.length(), Object.class);
        assertEquals(0, spans.length);
        TextUtils.copySpansFrom(source2, 0, Integer.MAX_VALUE, Object.class, dest6, 0);
        spans = dest6.getSpans(0, dest6.length(), Object.class);
        assertEquals(1, spans.length);

        // exceptional class kind
        dest6 = new SpannableString("exceptional test");
        TextUtils.copySpansFrom(source2, 0, source2.length(), null, dest6, 0);
        spans = dest6.getSpans(0, dest6.length(), Object.class);
        assertEquals(1, spans.length);

        // exceptional destination offset
        dest6 = new SpannableString("exceptional test");
        try {
            TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest6, -1);
            fail("Should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        try {
            TextUtils.copySpansFrom(source2, 0, source2.length(),
                    Object.class, dest6, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }

        // exceptional source
        try {
            TextUtils.copySpansFrom(null, 0, source2.length(), Object.class, dest6, 0);
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // expect
        }

        // exceptional destination
        try {
            TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, null, 0);
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // expect
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "ellipsize",
        args = {CharSequence.class, TextPaint.class, float.class, TruncateAt.class}
    )
    @ToBeFixed(bug = "1688347", explanation = "" +
            "1. the javadoc for ellipsize() is incomplete." +
            "   - doesn't explain @param and @return" +
            "   - doesn't describe expected behavior if user pass an exceptional argument." +
            "2. ellipsize() is not defined for TruncateAt.MARQUEE. " +
            "   In the code it looks like this does the same as MIDDLE. " +
            "   In other methods, MARQUEE is equivalent to END, except for the first line.")
    public void testEllipsize() {
        TextPaint p = new TextPaint();
        
        // turn off kerning. with kerning enabled, different methods of measuring the same text
        // produce different results.
        p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
        
        CharSequence text = "long string to truncate";

        float textWidth = p.measureText(mEllipsis + "uncate");
        assertEquals(mEllipsis + "uncate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString());

        textWidth = p.measureText("long str" + mEllipsis);
        assertEquals("long str" + mEllipsis,
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString());

        textWidth = p.measureText("long" + mEllipsis + "ate");
        assertEquals("long" + mEllipsis + "ate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString());

        // issue 1688347, ellipsize() is not defined for TruncateAt.MARQUEE.
        // In the code it looks like this does the same as MIDDLE.
        // In other methods, MARQUEE is equivalent to END, except for the first line.
        assertEquals("long" + mEllipsis + "ate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE).toString());

        textWidth = p.measureText(mEllipsis);
        assertEquals(mEllipsis, TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString());
        assertEquals("", TextUtils.ellipsize(text, p, textWidth - 1, TruncateAt.END).toString());
        assertEquals("", TextUtils.ellipsize(text, p, -1f, TruncateAt.END).toString());
        assertEquals(text,
                TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END).toString());

        assertEquals(mEllipsis,
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString());
        assertEquals(mEllipsis,
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString());

        try {
            TextUtils.ellipsize(text, null, textWidth, TruncateAt.MIDDLE);
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }

        try {
            TextUtils.ellipsize(null, p, textWidth, TruncateAt.MIDDLE);
            fail("Should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "ellipsize",
        args = {CharSequence.class, TextPaint.class, float.class, TruncateAt.class,
                boolean.class, EllipsizeCallback.class}
    )
    @ToBeFixed(bug = "1688347", explanation = "" +
            "1. the javadoc for ellipsize() is incomplete." +
            "   - doesn't explain @param and @return" +
            "   - doesn't describe expected behavior if user pass an exceptional argument." +
            "2. ellipsize() is not defined for TruncateAt.MARQUEE. " +
            "   In the code it looks like this does the same as MIDDLE. " +
            "   In other methods, MARQUEE is equivalent to END, except for the first line.")
    public void testEllipsizeCallback() {
        TextPaint p = new TextPaint();

        // turn off kerning. with kerning enabled, different methods of measuring the same text
        // produce different results.
        p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
        
        TextUtils.EllipsizeCallback callback = new TextUtils.EllipsizeCallback() {
            public void ellipsized(final int start, final int end) {
                mStart = start;
                mEnd = end;
            }
        };

        String text = "long string to truncate";

        // TruncateAt.START, does not specify preserveLength
        resetRange();
        float textWidth = p.measureText(mEllipsis + "uncate");
        assertEquals(mEllipsis + "uncate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.START, false,
                        callback).toString());
        assertEquals(0, mStart);
        assertEquals(text.length() - "uncate".length(), mEnd);

        // TruncateAt.START, specify preserveLength
        resetRange();
        int ellipsisNum = text.length() - "uncate".length();
        assertEquals(getBlankString(true, ellipsisNum) + "uncate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.START, true,
                        callback).toString());
        assertEquals(0, mStart);
        assertEquals(text.length() - "uncate".length(), mEnd);

        // TruncateAt.END, specify preserveLength
        resetRange();
        textWidth = p.measureText("long str" + mEllipsis);
        ellipsisNum = text.length() - "long str".length();
        assertEquals("long str" + getBlankString(true, ellipsisNum),
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString());
        assertEquals("long str".length(), mStart);
        assertEquals(text.length(), mEnd);

        // TruncateAt.MIDDLE, specify preserveLength
        resetRange();
        textWidth = p.measureText("long" + mEllipsis + "ate");
        ellipsisNum = text.length() - "long".length() - "ate".length();
        assertEquals("long" + getBlankString(true, ellipsisNum) + "ate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE, true,
                        callback).toString());
        assertEquals("long".length(), mStart);
        assertEquals(text.length() - "ate".length(), mEnd);

        // TruncateAt.MIDDLE, specify preserveLength, but does not specify callback.
        resetRange();
        assertEquals("long" + getBlankString(true, ellipsisNum) + "ate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE, true,
                        null).toString());
        assertEquals(-1, mStart);
        assertEquals(-1, mEnd);

        // TruncateAt.MARQUEE, specify preserveLength
        // issue 1688347, ellipsize() is not defined for TruncateAt.MARQUEE.
        // In the code it looks like this does the same as MIDDLE.
        // In other methods, MARQUEE is equivalent to END, except for the first line.
        resetRange();
        textWidth = p.measureText("long" + mEllipsis + "ate");
        ellipsisNum = text.length() - "long".length() - "ate".length();
        assertEquals("long" + getBlankString(true, ellipsisNum) + "ate",
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE, true,
                        callback).toString());
        assertEquals("long".length(), mStart);
        assertEquals(text.length() - "ate".length(), mEnd);

        // avail is not long enough for ELLIPSIS, and preserveLength is specified.
        resetRange();
        textWidth = p.measureText(mEllipsis);
        assertEquals(getBlankString(false, text.length()),
                TextUtils.ellipsize(text, p, textWidth - 1f, TruncateAt.END, true,
                        callback).toString());
        assertEquals(0, mStart);
        assertEquals(text.length(), mEnd);

        // avail is not long enough for ELLIPSIS, and preserveLength doesn't be specified.
        resetRange();
        assertEquals("",
                TextUtils.ellipsize(text, p, textWidth - 1f, TruncateAt.END, false,
                        callback).toString());
        assertEquals(0, mStart);
        assertEquals(text.length(), mEnd);

        // avail is long enough for ELLIPSIS, and preserveLength is specified.
        resetRange();
        assertEquals(getBlankString(true, text.length()),
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString());
        assertEquals(0, mStart);
        assertEquals(text.length(), mEnd);

        // avail is long enough for ELLIPSIS, and preserveLength doesn't be specified.
        resetRange();
        assertEquals(mEllipsis,
                TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, false,
                        callback).toString());
        assertEquals(0, mStart);
        assertEquals(text.length(), mEnd);

        // avail is long enough for the whole sentence.
        resetRange();
        assertEquals(text,
                TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END, true,
                        callback).toString());
        assertEquals(0, mStart);
        assertEquals(0, mEnd);

        textWidth = p.measureText("long str" + mEllipsis);
        try {
            TextUtils.ellipsize(text, null, textWidth, TruncateAt.END, true, callback);
        } catch (NullPointerException e) {
            // expected
        }

        try {
            TextUtils.ellipsize(null, p, textWidth, TruncateAt.END, true, callback);
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * Get a blank string which is filled up by '\uFEFF'.
     *
     * @param isNeedStart - boolean whether need to start with char '\u2026' in the string.
     * @param len - int length of string.
     * @return a blank string which is filled up by '\uFEFF'.
     */
    private String getBlankString(boolean isNeedStart, int len) {
        StringBuilder buf = new StringBuilder();

        int i = 0;
        if (isNeedStart) {
            buf.append('\u2026');
            i++;
        }
        for (; i < len; i++) {
            buf.append('\uFEFF');
        }

        return buf.toString();
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "equals",
        args = {CharSequence.class, CharSequence.class}
    )
    public void testEquals() {
        // compare with itself.
        // String is a subclass of CharSequence and overrides equals().
        String string = "same object";
        assertTrue(TextUtils.equals(string, string));

        // SpannableString is a subclass of CharSequence and does NOT override equals().
        SpannableString spanString = new SpannableString("same object");
        final String url = "www.test_url.com";
        spanString.setSpan(new URLSpan(url), 0, spanString.length(),
                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        assertTrue(TextUtils.equals(spanString, spanString));

        // compare with other objects which have same content.
        assertTrue(TextUtils.equals("different object", "different object"));

        SpannableString urlSpanString = new SpannableString("same content");
        SpannableString bgColorSpanString = new SpannableString(
                "same content");
        URLSpan urlSpan = new URLSpan(url);
        urlSpanString.setSpan(urlSpan, 0, urlSpanString.length(),
                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN);
        bgColorSpanString.setSpan(bgColorSpan, 0, bgColorSpanString.length(),
                Spanned.SPAN_INCLUSIVE_INCLUSIVE);

        assertTrue(TextUtils.equals(bgColorSpanString, urlSpanString));

        // compare with other objects which have different content.
        assertFalse(TextUtils.equals("different content A", "different content B"));
        assertFalse(TextUtils.equals(spanString, urlSpanString));
        assertFalse(TextUtils.equals(spanString, bgColorSpanString));

        // compare with null
        assertTrue(TextUtils.equals(null, null));
        assertFalse(TextUtils.equals(spanString, null));
        assertFalse(TextUtils.equals(null, string));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "expandTemplate",
        args = {CharSequence.class, CharSequence[].class}
    )
    @ToBeFixed(bug = "1695243", explanation =
            "the javadoc for expandTemplate() is incomplete." +
            "1. not clear what is supposed to happen if template or values is null." +
            "2. doesn't discuss the case that ^0 in template string.")
    public void testExpandTemplate() {
        // ^1 at the start of template string.
        assertEquals("value1 template to be expanded",
                TextUtils.expandTemplate("^1 template to be expanded", "value1").toString());
        // ^1 at the end of template string.
        assertEquals("template to be expanded value1",
                TextUtils.expandTemplate("template to be expanded ^1", "value1").toString());
        // ^1 in the middle of template string.
        assertEquals("template value1 to be expanded",
                TextUtils.expandTemplate("template ^1 to be expanded", "value1").toString());
        // ^1 followed by a '0'
        assertEquals("template value10 to be expanded",
                TextUtils.expandTemplate("template ^10 to be expanded", "value1").toString());
        // ^1 followed by a 'a'
        assertEquals("template value1a to be expanded",
                TextUtils.expandTemplate("template ^1a to be expanded", "value1").toString());
        // no ^1
        assertEquals("template ^a to be expanded",
                TextUtils.expandTemplate("template ^a to be expanded", "value1").toString());
        assertEquals("template to be expanded",
                TextUtils.expandTemplate("template to be expanded", "value1").toString());
        // two consecutive ^ in the input to produce a single ^ in the output.
        assertEquals("template ^ to be expanded",
                TextUtils.expandTemplate("template ^^ to be expanded", "value1").toString());
        // two ^ with a space in the middle.
        assertEquals("template ^ ^ to be expanded",
                TextUtils.expandTemplate("template ^ ^ to be expanded", "value1").toString());
        // ^1 follow a '^'
        assertEquals("template ^1 to be expanded",
                TextUtils.expandTemplate("template ^^1 to be expanded", "value1").toString());
        // ^1 followed by a '^'
        assertEquals("template value1^ to be expanded",
                TextUtils.expandTemplate("template ^1^ to be expanded", "value1").toString());

        // 9 replacement values
        final int MAX_SUPPORTED_VALUES_NUM = 9;
        CharSequence values[] = createCharSequenceArray(MAX_SUPPORTED_VALUES_NUM);
        String expected = "value1 value2 template value3 value4 to value5 value6" +
                " be value7 value8 expanded value9";
        String template = "^1 ^2 template ^3 ^4 to ^5 ^6 be ^7 ^8 expanded ^9";
        assertEquals(expected, TextUtils.expandTemplate(template, values).toString());

        //  only up to 9 replacement values are supported
        values = createCharSequenceArray(MAX_SUPPORTED_VALUES_NUM + 1);
        try {
            TextUtils.expandTemplate(template, values);
            fail("Should throw IllegalArgumentException!");
        } catch (IllegalArgumentException e) {
            // expect
        }

        // template string is ^0
        try {
            TextUtils.expandTemplate("template ^0 to be expanded", "value1");
        } catch (IllegalArgumentException e) {
            // issue 1695243, doesn't discuss the case that ^0 in template string.
        }

        // template string is ^0
        try {
            TextUtils.expandTemplate("template ^0 to be expanded");
        } catch (IllegalArgumentException e) {
            // issue 1695243, doesn't discuss the case that ^0 in template string.
        }

        // the template requests 2 values but only 1 is provided
        try {
            TextUtils.expandTemplate("template ^2 to be expanded", "value1");
            fail("Should throw IllegalArgumentException!");
        } catch (IllegalArgumentException e) {
            // expect
        }

        // values is null
        try {
            TextUtils.expandTemplate("template ^2 to be expanded", (CharSequence[]) null);
        } catch (NullPointerException e) {
            // expected
        }

        // the template requests 2 values but only one null value is provided
        try {
            TextUtils.expandTemplate("template ^2 to be expanded", (CharSequence) null);
            fail("Should throw IllegalArgumentException!");
        } catch (IllegalArgumentException e) {
            // expect
        }

        // the template requests 2 values and 2 values is provided, but all values are null.
        try {
            TextUtils.expandTemplate("template ^2 to be expanded",
                    (CharSequence) null, (CharSequence) null);
        } catch (NullPointerException e) {
            // expected
        }

        // the template requests 2 values but no value is provided.
        try {
            TextUtils.expandTemplate("template ^2 to be expanded");
            fail("Should throw IllegalArgumentException!");
        } catch (IllegalArgumentException e) {
            // expected
        }

        // template is null
        try {
            TextUtils.expandTemplate(null, "value1");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * Create a char sequence array with the specified length
     * @param len the length of the array
     * @return The char sequence array with the specified length.
     * The value of each item is "value[index+1]"
     */
    private CharSequence[] createCharSequenceArray(int len) {
        CharSequence array[] = new CharSequence[len];

        for (int i = 0; i < len; i++) {
            array[i] = "value" + (i + 1);
        }

        return array;
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getChars",
        args = {CharSequence.class, int.class, int.class, char[].class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for getChars() does not exist.")
    public void testGetChars() {
        char[] destOriginal = "destination".toCharArray();
        char[] destResult = destOriginal.clone();

        // check whether GetChars.getChars() is called and with the proper parameters.
        MockGetChars mockGetChars = new MockGetChars();
        int start = 1;
        int end = destResult.length;
        int destOff = 2;
        TextUtils.getChars(mockGetChars, start, end, destResult, destOff);
        assertTrue(mockGetChars.hasCalledGetChars());
        assertEquals(start, mockGetChars.ReadGetCharsParams().start);
        assertEquals(end, mockGetChars.ReadGetCharsParams().end);
        assertEquals(destResult, mockGetChars.ReadGetCharsParams().dest);
        assertEquals(destOff, mockGetChars.ReadGetCharsParams().destoff);

        // use MockCharSequence to do the test includes corner cases.
        MockCharSequence mockCharSequence = new MockCharSequence("source string mock");
        // get chars to place at the beginning of the destination except the latest one char.
        destResult = destOriginal.clone();
        start = 0;
        end = destResult.length - 1;
        destOff = 0;
        TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
        // chars before end are copied from the mockCharSequence.
        for (int i = 0; i < end - start; i++) {
            assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
        }
        // chars after end doesn't be changed.
        for (int i = destOff + (end - start); i < destOriginal.length; i++) {
            assertEquals(destOriginal[i], destResult[i]);
        }

        // get chars to place at the end of the destination except the earliest two chars.
        destResult = destOriginal.clone();
        start = 0;
        end = destResult.length - 2;
        destOff = 2;
        TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
        // chars before start doesn't be changed.
        for (int i = 0; i < destOff; i++) {
            assertEquals(destOriginal[i], destResult[i]);
        }
        // chars after start are copied from the mockCharSequence.
        for (int i = 0; i < end - start; i++) {
            assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
        }

        // get chars to place at the end of the destination except the earliest two chars
        // and the latest one word.
        destResult = destOriginal.clone();
        start = 1;
        end = destResult.length - 2;
        destOff = 0;
        TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
        for (int i = 0; i < destOff; i++) {
            assertEquals(destOriginal[i], destResult[i]);
        }
        for (int i = 0; i < end - start; i++) {
            assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
        }
        for (int i = destOff + (end - start); i < destOriginal.length; i++) {
            assertEquals(destOriginal[i], destResult[i]);
        }

        // get chars to place the whole of the destination
        destResult = destOriginal.clone();
        start = 0;
        end = destResult.length;
        destOff = 0;
        TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
        for (int i = 0; i < end - start; i++) {
            assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
        }

        // exceptional start.
        end = 2;
        destOff = 0;
        destResult = destOriginal.clone();
        try {
            TextUtils.getChars(mockCharSequence, -1, end, destResult, destOff);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        destResult = destOriginal.clone();
        TextUtils.getChars(mockCharSequence, Integer.MAX_VALUE, end, destResult, destOff);
        for (int i = 0; i < destResult.length; i++) {
            assertEquals(destOriginal[i], destResult[i]);
        }

        // exceptional end.
        destResult = destOriginal.clone();
        start = 0;
        destOff = 0;
        try {
            TextUtils.getChars(mockCharSequence, start, destResult.length + 1, destResult, destOff);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        destResult = destOriginal.clone();
        TextUtils.getChars(mockCharSequence, start, -1, destResult, destOff);
        for (int i = 0; i < destResult.length; i++) {
            assertEquals(destOriginal[i], destResult[i]);
        }

        // exceptional destOff.
        destResult = destOriginal.clone();
        start = 0;
        end = 2;
        try {
            TextUtils.getChars(mockCharSequence, start, end, destResult, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        try {
            TextUtils.getChars(mockCharSequence, start, end, destResult, Integer.MIN_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }

        // exceptional source
        start = 0;
        end = 2;
        destOff =0;
        try {
            TextUtils.getChars(null, start, end, destResult, destOff);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }

        // exceptional destination
        try {
            TextUtils.getChars(mockCharSequence, start, end, null, destOff);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * MockGetChars for test.
     */
    private class MockGetChars implements GetChars {
        private boolean mHasCalledGetChars;
        private GetCharsParams mGetCharsParams = new GetCharsParams();

        class GetCharsParams {
            int start;
            int end;
            char[] dest;
            int destoff;
        }

        public boolean hasCalledGetChars() {
            return mHasCalledGetChars;
        }

        public void reset() {
            mHasCalledGetChars = false;
        }

        public GetCharsParams ReadGetCharsParams() {
            return mGetCharsParams;
        }

        public void getChars(int start, int end, char[] dest, int destoff) {
            mHasCalledGetChars = true;
            mGetCharsParams.start = start;
            mGetCharsParams.end = end;
            mGetCharsParams.dest = dest;
            mGetCharsParams.destoff = destoff;
        }

        public char charAt(int arg0) {
            return 0;
        }

        public int length() {
            return 100;
        }

        public CharSequence subSequence(int arg0, int arg1) {
            return null;
        }
    }

    /**
     * MockCharSequence for test.
     */
    private class MockCharSequence implements CharSequence {
        private char mText[];

        public MockCharSequence() {
            this("");
        }

        public MockCharSequence(String text) {
            mText = text.toCharArray();
        }

        public char charAt(int arg0) {
            if (arg0 >= 0 && arg0 < mText.length) {
                return mText[arg0];
            }
            throw new IndexOutOfBoundsException();
        }

        public int length() {
            return mText.length;
        }

        public CharSequence subSequence(int arg0, int arg1) {
            return null;
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getOffsetAfter",
        args = {CharSequence.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for getOffsetAfter() does not exist.")
    public void testGetOffsetAfter() {
        // the first '\uD800' is index 9, the second 'uD800' is index 16
        // the '\uDBFF' is index 26
        final int POS_FIRST_D800 = 9;       // the position of the first '\uD800'.
        final int POS_SECOND_D800 = 16;
        final int POS_FIRST_DBFF = 26;
        final int SUPPLEMENTARY_CHARACTERS_OFFSET = 2;  // the offset for a supplementary characters
        final int NORMAL_CHARACTERS_OFFSET = 1;
        SpannableString text = new SpannableString(
                "string to\uD800\uDB00 get \uD800\uDC00 offset \uDBFF\uDFFF after");
        assertEquals(0 + 1, TextUtils.getOffsetAfter(text, 0));
        assertEquals(text.length(), TextUtils.getOffsetAfter(text, text.length()));
        assertEquals(text.length(), TextUtils.getOffsetAfter(text, text.length() - 1));
        assertEquals(POS_FIRST_D800 + NORMAL_CHARACTERS_OFFSET,
                TextUtils.getOffsetAfter(text, POS_FIRST_D800));
        assertEquals(POS_SECOND_D800 + SUPPLEMENTARY_CHARACTERS_OFFSET,
                TextUtils.getOffsetAfter(text, POS_SECOND_D800));
        assertEquals(POS_FIRST_DBFF + SUPPLEMENTARY_CHARACTERS_OFFSET,
                TextUtils.getOffsetAfter(text, POS_FIRST_DBFF));

        // the CharSequence string has a span.
        MockReplacementSpan mockReplacementSpan = new MockReplacementSpan();
        text.setSpan(mockReplacementSpan, POS_FIRST_D800 - 1, text.length() - 1,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        assertEquals(text.length() - 1, TextUtils.getOffsetAfter(text, POS_FIRST_D800));

        try {
            TextUtils.getOffsetAfter(text, -1);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.getOffsetAfter(text, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.getOffsetAfter(null, 0);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * MockReplacementSpan for test.
     */
    private class MockReplacementSpan extends ReplacementSpan {
        @Override
        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
                int y, int bottom, Paint paint) {
        }

        @Override
        public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
            return 0;
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getOffsetBefore",
        args = {CharSequence.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for getOffsetBefore() does not exist.")
    public void testGetOffsetBefore() {
        // the first '\uDC00' is index 10, the second 'uDC00' is index 17
        // the '\uDFFF' is index 27
        final int POS_FIRST_DC00 = 10;
        final int POS_SECOND_DC00 = 17;
        final int POS_FIRST_DFFF = 27;
        final int SUPPLYMENTARY_CHARACTERS_OFFSET = 2;
        final int NORMAL_CHARACTERS_OFFSET = 1;
        SpannableString text = new SpannableString(
                "string to\uD700\uDC00 get \uD800\uDC00 offset \uDBFF\uDFFF before");
        assertEquals(0, TextUtils.getOffsetBefore(text, 0));
        assertEquals(0, TextUtils.getOffsetBefore(text, 1));
        assertEquals(text.length() - 1, TextUtils.getOffsetBefore(text, text.length()));
        assertEquals(POS_FIRST_DC00 + 1 - NORMAL_CHARACTERS_OFFSET,
                TextUtils.getOffsetBefore(text, POS_FIRST_DC00 + 1));
        assertEquals(POS_SECOND_DC00 + 1 - SUPPLYMENTARY_CHARACTERS_OFFSET,
                TextUtils.getOffsetBefore(text, POS_SECOND_DC00 + 1));
        assertEquals(POS_FIRST_DFFF + 1 - SUPPLYMENTARY_CHARACTERS_OFFSET,
                TextUtils.getOffsetBefore(text, POS_FIRST_DFFF + 1));

        // the CharSequence string has a span.
        MockReplacementSpan mockReplacementSpan = new MockReplacementSpan();
        text.setSpan(mockReplacementSpan, 0, POS_FIRST_DC00 + 1,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        assertEquals(0, TextUtils.getOffsetBefore(text, POS_FIRST_DC00));

        try {
            TextUtils.getOffsetBefore(text, -1);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.getOffsetBefore(text, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.getOffsetBefore(null, POS_FIRST_DC00);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getReverse",
        args = {CharSequence.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for getReverse() does not exist.")
    public void testGetReverse() {
        String source = "string to be reversed";
        assertEquals("gnirts", TextUtils.getReverse(source, 0, "string".length()).toString());
        assertEquals("desrever",
                TextUtils.getReverse(source, source.length() - "reversed".length(),
                        source.length()).toString());
        assertEquals("", TextUtils.getReverse(source, 0, 0).toString());

        // issue 1695243, exception is thrown after the result of some cases
        // convert to a string, is this expected?
        CharSequence result = TextUtils.getReverse(source, -1, "string".length());
        try {
            result.toString();
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        TextUtils.getReverse(source, 0, source.length() + 1);
        try {
            result.toString();
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        TextUtils.getReverse(source, "string".length(), 0);
        try {
            result.toString();
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        TextUtils.getReverse(source, 0, Integer.MAX_VALUE);
        try {
            result.toString();
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        TextUtils.getReverse(source, Integer.MIN_VALUE, "string".length());
        try {
            result.toString();
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        TextUtils.getReverse(null, 0, "string".length());
        try {
            result.toString();
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getTrimmedLength",
        args = {CharSequence.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for getReverse() is incomplete." +
            "1. doesn't explain @param and @return." +
            "2. doesn't discuss the case that parameter is expectional.")
    public void testGetTrimmedLength() {
        assertEquals("normalstring".length(), TextUtils.getTrimmedLength("normalstring"));
        assertEquals("normal string".length(), TextUtils.getTrimmedLength("normal string"));
        assertEquals("blank before".length(), TextUtils.getTrimmedLength(" \t  blank before"));
        assertEquals("blank after".length(), TextUtils.getTrimmedLength("blank after   \n    "));
        assertEquals("blank both".length(), TextUtils.getTrimmedLength(" \t   blank both  \n "));

        char[] allTrimmedChars = new char[] {
                '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007',
                '\u0008', '\u0009', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015',
                '\u0016', '\u0017', '\u0018', '\u0019', '\u0020'
        };
        assertEquals(0, TextUtils.getTrimmedLength(String.valueOf(allTrimmedChars)));

        try {
            TextUtils.getTrimmedLength(null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "htmlEncode",
        args = {String.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for htmlEncode() is incomplete." +
            "1. doesn't discuss the case that parameter is expectional.")
    public void testHtmlEncode() {
        assertEquals("&lt;_html_&gt;\\ &amp;&quot;&apos;string&apos;&quot;",
                TextUtils.htmlEncode("<_html_>\\ &\"'string'\""));

         try {
             TextUtils.htmlEncode(null);
             fail("Should throw NullPointerException!");
         } catch (NullPointerException e) {
             // expected
         }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "indexOf",
        args = {CharSequence.class, char.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for indexOf() does not exist.")
    public void testIndexOf1() {
        String searchString = "string to be searched";
        final int INDEX_OF_FIRST_R = 2;     // first occurrence of 'r'
        final int INDEX_OF_FIRST_T = 1;
        final int INDEX_OF_FIRST_D = searchString.length() - 1;

        assertEquals(INDEX_OF_FIRST_T, TextUtils.indexOf(searchString, 't'));
        assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r'));
        assertEquals(INDEX_OF_FIRST_D, TextUtils.indexOf(searchString, 'd'));
        assertEquals(-1, TextUtils.indexOf(searchString, 'f'));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(stringBuffer, 'r'));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(stringBuilder, 'r'));

        MockGetChars mockGetChars = new MockGetChars();
        assertFalse(mockGetChars.hasCalledGetChars());
        TextUtils.indexOf(mockGetChars, 'r');
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(mockCharSequence, 'r'));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "indexOf",
        args = {CharSequence.class, char.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for indexOf() does not exist.")
    public void testIndexOf2() {
        String searchString = "string to be searched";
        final int INDEX_OF_FIRST_R = 2;
        final int INDEX_OF_SECOND_R = 16;

        assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r', 0));
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(searchString, 'r', INDEX_OF_FIRST_R + 1));
        assertEquals(-1, TextUtils.indexOf(searchString, 'r', searchString.length()));
        assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r', Integer.MIN_VALUE));
        assertEquals(2, TextUtils.indexOf(searchString, 'r', Integer.MAX_VALUE));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuffer, 'r', INDEX_OF_FIRST_R + 1));
        try {
            TextUtils.indexOf(stringBuffer, 'r', Integer.MIN_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        assertEquals(-1, TextUtils.indexOf(stringBuffer, 'r', Integer.MAX_VALUE));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_SECOND_R,
                TextUtils.indexOf(stringBuilder, 'r', INDEX_OF_FIRST_R + 1));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.indexOf(mockGetChars, 'r', INDEX_OF_FIRST_R + 1);
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(mockCharSequence, 'r',
                INDEX_OF_FIRST_R + 1));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "indexOf",
        args = {CharSequence.class, char.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for indexOf() does not exist.")
    public void testIndexOf3() {
        String searchString = "string to be searched";
        final int INDEX_OF_FIRST_R = 2;
        final int INDEX_OF_SECOND_R = 16;

        assertEquals(INDEX_OF_FIRST_R,
                TextUtils.indexOf(searchString, 'r', 0, searchString.length()));
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(searchString, 'r',
                INDEX_OF_FIRST_R + 1, searchString.length()));
        assertEquals(-1, TextUtils.indexOf(searchString, 'r',
                INDEX_OF_FIRST_R + 1, INDEX_OF_SECOND_R));

        try {
            TextUtils.indexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        assertEquals(-1,
                TextUtils.indexOf(searchString, 'r', Integer.MAX_VALUE, INDEX_OF_SECOND_R));
        assertEquals(-1, TextUtils.indexOf(searchString, 'r', 0, Integer.MIN_VALUE));
        try {
            TextUtils.indexOf(searchString, 'r', 0, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuffer, 'r',
                INDEX_OF_FIRST_R + 1, searchString.length()));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuilder, 'r',
                INDEX_OF_FIRST_R + 1, searchString.length()));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.indexOf(mockGetChars, 'r', INDEX_OF_FIRST_R + 1, searchString.length());
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(mockCharSequence, 'r',
                INDEX_OF_FIRST_R + 1, searchString.length()));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "indexOf",
        args = {CharSequence.class, CharSequence.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for indexOf() does not exist.")
    public void testIndexOf4() {
        String searchString = "string to be searched by string";
        final int SEARCH_INDEX = 13;

        assertEquals(0, TextUtils.indexOf(searchString, "string"));
        assertEquals(SEARCH_INDEX, TextUtils.indexOf(searchString, "search"));
        assertEquals(-1, TextUtils.indexOf(searchString, "tobe"));
        assertEquals(0, TextUtils.indexOf(searchString, ""));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(SEARCH_INDEX, TextUtils.indexOf(stringBuffer, "search"));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(SEARCH_INDEX, TextUtils.indexOf(stringBuilder, "search"));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.indexOf(mockGetChars, "search");
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(SEARCH_INDEX, TextUtils.indexOf(mockCharSequence, "search"));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "indexOf",
        args = {CharSequence.class, CharSequence.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for indexOf() does not exist.")
    public void testIndexOf5() {
        String searchString = "string to be searched by string";
        final int INDEX_OF_FIRST_STRING = 0;
        final int INDEX_OF_SECOND_STRING = 25;

        assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 0));
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
                INDEX_OF_FIRST_STRING + 1));
        assertEquals(-1, TextUtils.indexOf(searchString, "string", INDEX_OF_SECOND_STRING + 1));
        assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string",
                Integer.MIN_VALUE));
        assertEquals(0, TextUtils.indexOf(searchString, "string", Integer.MAX_VALUE));

        assertEquals(1, TextUtils.indexOf(searchString, "", 1));
        assertEquals(Integer.MAX_VALUE, TextUtils.indexOf(searchString, "", Integer.MAX_VALUE));

        assertEquals(0, TextUtils.indexOf(searchString, searchString, 0));
        assertEquals(-1, TextUtils.indexOf(searchString, searchString + "longer needle", 0));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, "string",
                INDEX_OF_FIRST_STRING + 1));
        try {
            TextUtils.indexOf(stringBuffer, "string", Integer.MIN_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        assertEquals(-1, TextUtils.indexOf(stringBuffer, "string", Integer.MAX_VALUE));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuilder, "string",
                INDEX_OF_FIRST_STRING + 1));

        MockGetChars mockGetChars = new MockGetChars();
        assertFalse(mockGetChars.hasCalledGetChars());
        TextUtils.indexOf(mockGetChars, "string", INDEX_OF_FIRST_STRING + 1);
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(mockCharSequence, "string",
                INDEX_OF_FIRST_STRING + 1));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "indexOf",
        args = {CharSequence.class, CharSequence.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for indexOf() does not exist.")
    public void testIndexOf6() {
        String searchString = "string to be searched by string";
        final int INDEX_OF_FIRST_STRING = 0;
        final int INDEX_OF_SECOND_STRING = 25;

        assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 0,
                searchString.length()));
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
                INDEX_OF_FIRST_STRING + 1, searchString.length()));
        assertEquals(-1, TextUtils.indexOf(searchString, "string", INDEX_OF_FIRST_STRING + 1,
                INDEX_OF_SECOND_STRING - 1));
        assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string",
                Integer.MIN_VALUE, INDEX_OF_SECOND_STRING - 1));
        assertEquals(0, TextUtils.indexOf(searchString, "string", Integer.MAX_VALUE,
                INDEX_OF_SECOND_STRING - 1));

        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
                INDEX_OF_FIRST_STRING + 1, Integer.MIN_VALUE));
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
                INDEX_OF_FIRST_STRING + 1, Integer.MAX_VALUE));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, "string",
                INDEX_OF_FIRST_STRING + 1, searchString.length()));
        try {
            TextUtils.indexOf(stringBuffer, "string", Integer.MIN_VALUE,
                    INDEX_OF_SECOND_STRING - 1);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        assertEquals(-1, TextUtils.indexOf(stringBuffer, "string", Integer.MAX_VALUE,
                searchString.length()));
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer,
                "string", INDEX_OF_FIRST_STRING + 1, Integer.MIN_VALUE));
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer,
                "string", INDEX_OF_FIRST_STRING + 1, Integer.MAX_VALUE));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuilder, "string",
                INDEX_OF_FIRST_STRING + 1, searchString.length()));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.indexOf(mockGetChars, "string", INDEX_OF_FIRST_STRING + 1, searchString.length());
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(mockCharSequence, "string",
                INDEX_OF_FIRST_STRING + 1, searchString.length()));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "isDigitsOnly",
        args = {CharSequence.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for isDigitsOnly() is incomplete.")
    public void testIsDigitsOnly() {
        assertFalse(TextUtils.isDigitsOnly("no digit"));
        assertFalse(TextUtils.isDigitsOnly("character and 56 digits"));
        assertTrue(TextUtils.isDigitsOnly("0123456789"));
        assertFalse(TextUtils.isDigitsOnly("1234 56789"));

        try {
            TextUtils.isDigitsOnly(null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // issue 1695243, not clear what is supposed result if the CharSequence is null.
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "isEmpty",
        args = {CharSequence.class}
    )
    public void testIsEmpty() {
        assertFalse(TextUtils.isEmpty("not empty"));
        assertFalse(TextUtils.isEmpty("    "));
        assertTrue(TextUtils.isEmpty(""));
        assertTrue(TextUtils.isEmpty(null));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "isGraphic",
        args = {char.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for isGraphic() is incomplete.")
    public void testIsGraphicChar() {
        assertTrue(TextUtils.isGraphic('a'));
        assertTrue(TextUtils.isGraphic("\uBA00"));

        // LINE_SEPARATOR
        assertFalse(TextUtils.isGraphic('\u2028'));

        // PARAGRAPH_SEPARATOR
        assertFalse(TextUtils.isGraphic('\u2029'));

        // CONTROL
        assertFalse(TextUtils.isGraphic('\u0085'));

        // UNASSIGNED
        assertFalse(TextUtils.isGraphic('\u0D00'));

        // SURROGATE
        assertFalse(TextUtils.isGraphic('\uD800'));

        // SPACE_SEPARATOR
        assertFalse(TextUtils.isGraphic('\u0020'));

        try {
            assertFalse(TextUtils.isGraphic((Character) null));
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "isGraphic",
        args = {CharSequence.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for isGraphic() is incomplete.")
    public void testIsGraphicCharSequence() {
        assertTrue(TextUtils.isGraphic("printable characters"));

        assertFalse(TextUtils.isGraphic("\u2028\u2029\u0085\u0D00\uD800\u0020"));

        assertTrue(TextUtils.isGraphic("a\u2028\u2029\u0085\u0D00\uD800\u0020"));

        try {
            TextUtils.isGraphic(null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @SuppressWarnings("unchecked")
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "join",
        args = {CharSequence.class, Iterable.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for join() is incomplete.")
    public void testJoin1() {
        ArrayList<CharSequence> charTokens = new ArrayList<CharSequence>();
        charTokens.add("string1");
        charTokens.add("string2");
        charTokens.add("string3");
        assertEquals("string1|string2|string3", TextUtils.join("|", charTokens));
        assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens));
        assertEquals("string1string2string3", TextUtils.join("", charTokens));

        // issue 1695243, not clear what is supposed result if the delimiter or tokens are null.
        assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens));
        try {
            TextUtils.join("|", (Iterable) null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }

        ArrayList<SpannableString> spannableStringTokens = new ArrayList<SpannableString>();
        spannableStringTokens.add(new SpannableString("span 1"));
        spannableStringTokens.add(new SpannableString("span 2"));
        spannableStringTokens.add(new SpannableString("span 3"));
        assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "join",
        args = {CharSequence.class, Object[].class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for join() is incomplete.")
    public void testJoin2() {
        CharSequence[] charTokens = new CharSequence[] { "string1", "string2", "string3" };
        assertEquals("string1|string2|string3", TextUtils.join("|", charTokens));
        assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens));
        assertEquals("string1string2string3", TextUtils.join("", charTokens));

        // issue 1695243, not clear what is supposed result if the delimiter or tokens are null.
        assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens));
        try {
            TextUtils.join("|", (Object[]) null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }

        SpannableString[] spannableStringTokens = new SpannableString[] {
                new SpannableString("span 1"),
                new SpannableString("span 2"),
                new SpannableString("span 3") };
        assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "lastIndexOf",
        args = {CharSequence.class, char.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for lastIndexOf() does not exist.")
    public void testLastIndexOf1() {
        String searchString = "string to be searched";
        final int INDEX_OF_LAST_R = 16;
        final int INDEX_OF_LAST_T = 7;
        final int INDEX_OF_LAST_D = searchString.length() - 1;

        assertEquals(INDEX_OF_LAST_T, TextUtils.lastIndexOf(searchString, 't'));
        assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(searchString, 'r'));
        assertEquals(INDEX_OF_LAST_D, TextUtils.lastIndexOf(searchString, 'd'));
        assertEquals(-1, TextUtils.lastIndexOf(searchString, 'f'));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuffer, 'r'));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuilder, 'r'));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.lastIndexOf(mockGetChars, 'r');
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(mockCharSequence, 'r'));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "lastIndexOf",
        args = {CharSequence.class, char.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for lastIndexOf() does not exist.")
    public void testLastIndexOf2() {
        String searchString = "string to be searched";
        final int INDEX_OF_FIRST_R = 2;
        final int INDEX_OF_SECOND_R = 16;

        assertEquals(INDEX_OF_SECOND_R,
                TextUtils.lastIndexOf(searchString, 'r', searchString.length()));
        assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0));
        assertEquals(INDEX_OF_FIRST_R,
                TextUtils.lastIndexOf(searchString, 'r', INDEX_OF_FIRST_R));
        assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE));
        assertEquals(INDEX_OF_SECOND_R,
                TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_FIRST_R,
                TextUtils.lastIndexOf(stringBuffer, 'r', INDEX_OF_FIRST_R));
        assertEquals(-1, TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MIN_VALUE));
        assertEquals(INDEX_OF_SECOND_R,
                TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MAX_VALUE));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_FIRST_R,
                TextUtils.lastIndexOf(stringBuilder, 'r', INDEX_OF_FIRST_R));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.lastIndexOf(mockGetChars, 'r', INDEX_OF_FIRST_R);
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_FIRST_R,
                TextUtils.lastIndexOf(mockCharSequence, 'r', INDEX_OF_FIRST_R));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "lastIndexOf",
        args = {CharSequence.class, char.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for lastIndexOf() does not exist.")
    public void testLastIndexOf3() {
        String searchString = "string to be searched";
        final int INDEX_OF_FIRST_R = 2;
        final int INDEX_OF_SECOND_R = 16;

        assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0,
                searchString.length()));
        assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(searchString, 'r', 0,
                INDEX_OF_SECOND_R - 1));
        assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, INDEX_OF_FIRST_R - 1));

        try {
            TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R - 1);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expect
        }
        assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE,
                INDEX_OF_SECOND_R - 1));
        assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, Integer.MIN_VALUE));
        assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0,
                Integer.MAX_VALUE));

        StringBuffer stringBuffer = new StringBuffer(searchString);
        assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuffer, 'r', 0,
                INDEX_OF_SECOND_R - 1));

        StringBuilder stringBuilder = new StringBuilder(searchString);
        assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuilder, 'r', 0,
                INDEX_OF_SECOND_R - 1));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.lastIndexOf(mockGetChars, 'r', 0, INDEX_OF_SECOND_R - 1);
        assertTrue(mockGetChars.hasCalledGetChars());

        MockCharSequence mockCharSequence = new MockCharSequence(searchString);
        assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(mockCharSequence, 'r', 0,
                INDEX_OF_SECOND_R - 1));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "regionMatches",
        args = {CharSequence.class, int.class, CharSequence.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for regionMatches() does not exist.")
    public void testRegionMatches() {
        assertFalse(TextUtils.regionMatches("one", 0, "two", 0, "one".length()));
        assertTrue(TextUtils.regionMatches("one", 0, "one", 0, "one".length()));
        try {
            TextUtils.regionMatches("one", 0, "one", 0, "one".length() + 1);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        String one = "Hello Android, hello World!";
        String two = "Hello World";
        // match "Hello"
        assertTrue(TextUtils.regionMatches(one, 0, two, 0, "Hello".length()));

        // match "Hello A" and "Hello W"
        assertFalse(TextUtils.regionMatches(one, 0, two, 0, "Hello A".length()));

        // match "World"
        assertTrue(TextUtils.regionMatches(one, "Hello Android, hello ".length(),
                two, "Hello ".length(), "World".length()));
        assertFalse(TextUtils.regionMatches(one, "Hello Android, hello ".length(),
                two, 0, "World".length()));

        try {
            TextUtils.regionMatches(one, Integer.MIN_VALUE, two, 0, "Hello".length());
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }
        try {
            TextUtils.regionMatches(one, Integer.MAX_VALUE, two, 0, "Hello".length());
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.regionMatches(one, 0, two, Integer.MIN_VALUE, "Hello".length());
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }
        try {
            TextUtils.regionMatches(one, 0, two, Integer.MAX_VALUE, "Hello".length());
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.regionMatches(one, 0, two, 0, Integer.MIN_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }
        try {
            TextUtils.regionMatches(one, 0, two, 0, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
        }

        try {
            TextUtils.regionMatches(null, 0, two, 0, "Hello".length());
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }
        try {
            TextUtils.regionMatches(one, 0, null, 0, "Hello".length());
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "replace",
        args = {CharSequence.class, String[].class, CharSequence[].class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for replace() is incomplete.")
    public void testReplace() {
        String template = "this is a string to be as the template for replacement";

        String sources[] = new String[] { "string" };
        CharSequence destinations[] = new CharSequence[] { "text" };
        SpannableStringBuilder replacedString = (SpannableStringBuilder) TextUtils.replace(template,
                sources, destinations);
        assertEquals("this is a text to be as the template for replacement",
                replacedString.toString());

        sources = new String[] {"is", "the", "for replacement"};
        destinations = new CharSequence[] {"was", "", "to be replaced"};
        replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations);
        assertEquals("thwas is a string to be as  template to be replaced",
                replacedString.toString());

        sources = new String[] {"is", "for replacement"};
        destinations = new CharSequence[] {"was", "", "to be replaced"};
        replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations);
        assertEquals("thwas is a string to be as the template ", replacedString.toString());

        sources = new String[] {"is", "the", "for replacement"};
        destinations = new CharSequence[] {"was", "to be replaced"};
        try {
            TextUtils.replace(template, sources, destinations);
            fail("Should throw ArrayIndexOutOfBoundsException!");
        } catch (ArrayIndexOutOfBoundsException e) {
            // expected
        }

        try {
            TextUtils.replace(null, sources, destinations);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
        try {
            TextUtils.replace(template, null, destinations);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
        try {
            TextUtils.replace(template, sources, null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "split",
        args = {String.class, Pattern.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for split() is incomplete." +
            "1. not clear what is supposed result if the pattern string is empty.")
    public void testSplitPattern() {
        String testString = "abccbadecdebz";
        assertEquals(calculateCharsCount(testString, "c") + 1,
                TextUtils.split(testString, Pattern.compile("c")).length);
        assertEquals(calculateCharsCount(testString, "a") + 1,
                TextUtils.split(testString, Pattern.compile("a")).length);
        assertEquals(calculateCharsCount(testString, "z") + 1,
                TextUtils.split(testString, Pattern.compile("z")).length);
        assertEquals(calculateCharsCount(testString, "de") + 1,
                TextUtils.split(testString, Pattern.compile("de")).length);
        int totalCount = 1 + calculateCharsCount(testString, "a")
                + calculateCharsCount(testString, "b") + calculateCharsCount(testString, "c");
        assertEquals(totalCount,
                TextUtils.split(testString, Pattern.compile("[a-c]")).length);
        assertEquals(0, TextUtils.split("", Pattern.compile("a")).length);
        // issue 1695243, not clear what is supposed result if the pattern string is empty.
        assertEquals(testString.length() + 2,
                TextUtils.split(testString, Pattern.compile("")).length);

        try {
            TextUtils.split(null, Pattern.compile("a"));
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }
        try {
            TextUtils.split("abccbadecdebz", (Pattern) null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }
    }

    /*
     * return the appearance count of searched chars in text.
     */
    private int calculateCharsCount(CharSequence text, CharSequence searches) {
        int count = 0;
        int start = TextUtils.indexOf(text, searches, 0);

        while (start != -1) {
            count++;
            start = TextUtils.indexOf(text, searches, start + 1);
        }
        return count;
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "split",
        args = {String.class, String.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for split() is incomplete." +
            "1. not clear what is supposed result if the pattern string is empty.")
    public void testSplitString() {
        String testString = "abccbadecdebz";
        assertEquals(calculateCharsCount(testString, "c") + 1,
                TextUtils.split("abccbadecdebz", "c").length);
        assertEquals(calculateCharsCount(testString, "a") + 1,
                TextUtils.split("abccbadecdebz", "a").length);
        assertEquals(calculateCharsCount(testString, "z") + 1,
                TextUtils.split("abccbadecdebz", "z").length);
        assertEquals(calculateCharsCount(testString, "de") + 1,
                TextUtils.split("abccbadecdebz", "de").length);
        assertEquals(0, TextUtils.split("", "a").length);
        // issue 1695243, not clear what is supposed result if the pattern string is empty.
        assertEquals(testString.length() + 2,
                TextUtils.split("abccbadecdebz", "").length);

        try {
            TextUtils.split(null, "a");
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }
        try {
            TextUtils.split("abccbadecdebz", (String) null);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expect
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "stringOrSpannedString",
        args = {CharSequence.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for" +
            " stringOrSpannedString() does not exist.")
    public void testStringOrSpannedString() {
        assertNull(TextUtils.stringOrSpannedString(null));

        SpannedString spannedString = new SpannedString("Spanned String");
        assertSame(spannedString, TextUtils.stringOrSpannedString(spannedString));

        SpannableString spannableString = new SpannableString("Spannable String");
        assertEquals("Spannable String",
                TextUtils.stringOrSpannedString(spannableString).toString());
        assertEquals(SpannedString.class,
                TextUtils.stringOrSpannedString(spannableString).getClass());

        StringBuffer stringBuffer = new StringBuffer("String Buffer");
        assertEquals("String Buffer",
                TextUtils.stringOrSpannedString(stringBuffer).toString());
        assertEquals(String.class,
                TextUtils.stringOrSpannedString(stringBuffer).getClass());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "substring",
        args = {CharSequence.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for substring() is incomplete." +
            "1. doesn't explain @param and @return" +
            "2. not clear what is supposed to happen if source is null." +
            "3. doesn't explain the thrown IndexOutOfBoundsException")
    public void testSubString() {
        String string = "String";
        assertSame(string, TextUtils.substring(string, 0, string.length()));
        assertEquals("Strin", TextUtils.substring(string, 0, string.length() - 1));
        assertEquals("", TextUtils.substring(string, 1, 1));

        try {
            TextUtils.substring(string, string.length(), 0);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            TextUtils.substring(string, -1, string.length());
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            TextUtils.substring(string, Integer.MAX_VALUE, string.length());
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            TextUtils.substring(string, 0, -1);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            TextUtils.substring(string, 0, Integer.MAX_VALUE);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            TextUtils.substring(null, 0, string.length());
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }

        StringBuffer stringBuffer = new StringBuffer("String Buffer");
        assertEquals("Strin", TextUtils.substring(stringBuffer, 0, string.length() - 1));
        assertEquals("", TextUtils.substring(stringBuffer, 1, 1));

        MockGetChars mockGetChars = new MockGetChars();
        TextUtils.substring(mockGetChars, 0, string.length());
        assertTrue(mockGetChars.hasCalledGetChars());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "writeToParcel",
        args = {CharSequence.class, Parcel.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for writeToParcel() is incomplete." +
            "1. doesn't explain @param and @return" +
            "2. not clear is it the supposed result when the CharSequence is null.")
    public void testWriteToParcel() {
        Parcel p = Parcel.obtain();

        Parcelable.Creator<CharSequence> creator = TextUtils.CHAR_SEQUENCE_CREATOR;

        String string = "String";
        TextUtils.writeToParcel(string, p, 0);
        p.setDataPosition(0);
        assertEquals(string, creator.createFromParcel(p).toString());
        p.recycle();

        p = Parcel.obtain();
        TextUtils.writeToParcel(null, p, 0);
        p.setDataPosition(0);
        assertNull(creator.createFromParcel(p));
        p.recycle();

        p = Parcel.obtain();
        SpannableString spannableString = new SpannableString("Spannable String");
        URLSpan urlSpan = new URLSpan("URL Span");
        int urlSpanStart = spannableString.length() >> 1;
        int urlSpanEnd = spannableString.length();
        spannableString.setSpan(urlSpan, urlSpanStart, urlSpanEnd,
                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        TextUtils.writeToParcel(spannableString, p, 0);
        p.setDataPosition(0);
        SpannableString ret = (SpannableString) creator.createFromParcel(p);
        assertEquals("Spannable String", ret.toString());
        Object[] spans = ret.getSpans(0, ret.length(), Object.class);
        assertEquals(1, spans.length);
        assertEquals("URL Span", ((URLSpan) spans[0]).getURL());
        assertEquals(urlSpanStart, ret.getSpanStart(spans[0]));
        assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0]));
        assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0]));
        p.recycle();

        p = Parcel.obtain();
        ColorStateList colors = new ColorStateList(new int[][] {
                new int[] {android.R.attr.state_focused}, new int[0]},
                new int[] {Color.rgb(0, 255, 0), Color.BLACK});
        int textSize = 20;
        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(
                null, Typeface.ITALIC, textSize, colors, null);
        int textAppearanceSpanStart = 0;
        int textAppearanceSpanEnd = spannableString.length() >> 1;
        spannableString.setSpan(textAppearanceSpan, textAppearanceSpanStart,
                textAppearanceSpanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        TextUtils.writeToParcel(spannableString, p, -1);
        p.setDataPosition(0);
        ret = (SpannableString) creator.createFromParcel(p);
        assertEquals("Spannable String", ret.toString());
        spans = ret.getSpans(0, ret.length(), Object.class);
        assertEquals(2, spans.length);
        assertEquals("URL Span", ((URLSpan) spans[0]).getURL());
        assertEquals(urlSpanStart, ret.getSpanStart(spans[0]));
        assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0]));
        assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0]));
        assertEquals(null, ((TextAppearanceSpan) spans[1]).getFamily());

        assertEquals(Typeface.ITALIC, ((TextAppearanceSpan) spans[1]).getTextStyle());
        assertEquals(textSize, ((TextAppearanceSpan) spans[1]).getTextSize());

        assertEquals(colors.toString(), ((TextAppearanceSpan) spans[1]).getTextColor().toString());
        assertEquals(null, ((TextAppearanceSpan) spans[1]).getLinkTextColor());
        assertEquals(textAppearanceSpanStart, ret.getSpanStart(spans[1]));
        assertEquals(textAppearanceSpanEnd, ret.getSpanEnd(spans[1]));
        assertEquals(Spanned.SPAN_INCLUSIVE_EXCLUSIVE, ret.getSpanFlags(spans[1]));
        p.recycle();

        try {
            TextUtils.writeToParcel(spannableString, null, 0);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getCapsMode",
        args = {CharSequence.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1586346", explanation = "return cap mode which is NOT set in reqModes")
    public void testGetCapsMode() {
        final int CAP_MODE_ALL = TextUtils.CAP_MODE_CHARACTERS
                | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
        final int CAP_MODE_CHARACTERS_AND_WORD =
                TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS;
        String testString = "Start. Sentence word!No space before\n\t" +
                "Paragraph? (\"\'skip begin\'\"). skip end";

        // CAP_MODE_SENTENCES should be in effect in the whole text.
        for (int i = 0; i < testString.length(); i++) {
            assertEquals(TextUtils.CAP_MODE_CHARACTERS,
                    TextUtils.getCapsMode(testString, i, TextUtils.CAP_MODE_CHARACTERS));
        }

        // all modes should be in effect at the start of the text.
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_WORDS));
        // issue 1586346
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_SENTENCES));
        assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
                TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL));

        // all mode should be in effect at the position after "." or "?" or "!" + " ".
        int offset = testString.indexOf("Sentence word!");
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
        assertEquals(TextUtils.CAP_MODE_SENTENCES,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
        // issue 1586346
        assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
                TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL));

        // CAP_MODE_SENTENCES should NOT be in effect at the position after other words + " ".
        offset = testString.indexOf("word!");
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
        assertEquals(0,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
        // issue 1586346
        assertEquals(TextUtils.CAP_MODE_CHARACTERS,
                TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));

        // if no space after "." or "?" or "!", CAP_MODE_SENTENCES and CAP_MODE_WORDS
        // should NOT be in effect.
        offset = testString.indexOf("No space before");
        assertEquals(0,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
        assertEquals(0,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
        assertEquals(TextUtils.CAP_MODE_CHARACTERS,
                TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));

        // all mode should be in effect at a beginning of a new paragraph.
        offset = testString.indexOf("Paragraph");
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
        // issue 1586346
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
        assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
                TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));

        // some special word which means the start of a sentence should be skipped.
        offset = testString.indexOf("skip begin");
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
        assertEquals(TextUtils.CAP_MODE_SENTENCES,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
        // issue 1586346
        assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS,
                TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));

        // some special word which means the end of a sentence should be skipped.
        offset = testString.indexOf("skip end");
        assertEquals(TextUtils.CAP_MODE_WORDS,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
        assertEquals(TextUtils.CAP_MODE_SENTENCES,
                TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
        // issue 1586346
        assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS,
                TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getCapsMode",
        args = {CharSequence.class, int.class, int.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for substring() is incomplete." +
            "1. doesn't describe the expected result when parameter is exceptional.")
    public void testGetCapsModeException() {
        String testString = "Start. Sentence word!No space before\n\t" +
                "Paragraph? (\"\'skip begin\'\"). skip end";

        int offset = testString.indexOf("Sentence word!");
        assertEquals(TextUtils.CAP_MODE_CHARACTERS,
                TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_CHARACTERS));

        try {
            TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_SENTENCES);
            fail("Should throw NullPointerException!");
        } catch (NullPointerException e) {
            // expected
        }

        assertEquals(0, TextUtils.getCapsMode(testString, -1, TextUtils.CAP_MODE_SENTENCES));

        try {
            TextUtils.getCapsMode(testString, testString.length() + 1,
                    TextUtils.CAP_MODE_SENTENCES);
            fail("Should throw IndexOutOfBoundsException!");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "dumpSpans",
        args = {java.lang.CharSequence.class, android.util.Printer.class, java.lang.String.class}
    )
    public void testDumpSpans() {
        StringBuilder builder = new StringBuilder();
        StringBuilderPrinter printer = new StringBuilderPrinter(builder);
        CharSequence source = "test dump spans";
        String prefix = "prefix";

        assertEquals(0, builder.length());
        TextUtils.dumpSpans(source, printer, prefix);
        assertTrue(builder.length() > 0);

        builder = new StringBuilder();
        printer = new StringBuilderPrinter(builder);
        assertEquals(0, builder.length());
        SpannableString spanned = new SpannableString(source);
        spanned.setSpan(new Object(), 0, source.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        TextUtils.dumpSpans(spanned, printer, prefix);
        assertTrue(builder.length() > 0);
    }
}
