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

Commit c5b1675b authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Remove extra line in status updates"

parents 323c5906 3ab9a630
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@
    android:paddingBottom="8dip"
    android:gravity="center_vertical"
    android:paddingLeft="@dimen/account_container_left_padding"
    android:paddingRight="32dip">
    android:paddingRight="28dip">

    <LinearLayout
        android:id="@+id/account"
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@
    android:orientation="horizontal"
    android:gravity="center_vertical"
    android:paddingLeft="@dimen/account_container_left_padding"
    android:paddingRight="32dip">
    android:paddingRight="28dip">

    <LinearLayout
        android:id="@+id/account"
+10 −9
Original line number Diff line number Diff line
@@ -350,15 +350,16 @@ public class ContactDetailDisplayUtils {
                R.id.stream_item_attribution);
        TextView commentsView = (TextView) rootView.findViewById(R.id.stream_item_comments);
        ImageGetter imageGetter = new DefaultImageGetter(context.getPackageManager());
        htmlView.setText(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null));
        attributionView.setText(ContactBadgeUtil.getSocialDate(streamItem, context));
        if (streamItem.getComments() != null) {
            commentsView.setText(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
                    null));
            commentsView.setVisibility(View.VISIBLE);
        } else {
            commentsView.setVisibility(View.GONE);
        }

        // Stream item text
        setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null),
                htmlView);
        // Attribution
        setDataOrHideIfNone(ContactBadgeUtil.getSocialDate(streamItem, context),
                attributionView);
        // Comments
        setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
                null), commentsView);
        return rootView;
    }

+47 −17
Original line number Diff line number Diff line
package com.android.contacts.util;

import com.android.contacts.R;
import com.google.common.annotations.VisibleForTesting;

import android.content.Context;
import android.content.res.Resources;
import android.text.Html;
@@ -11,8 +14,6 @@ import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.text.style.QuoteSpan;

import com.android.contacts.R;

/**
 * Provides static functions to perform custom HTML to text conversions.
 * Specifically, it adjusts the color and padding of the vertical
@@ -21,43 +22,53 @@ import com.android.contacts.R;
public class HtmlUtils {

    /**
     * Converts HTML string to a {@link Spanned} text, adjusting formatting.
     * Converts HTML string to a {@link Spanned} text, adjusting formatting. Any extra new line
     * characters at the end of the text will be trimmed.
     */
    public static Spanned fromHtml(Context context, String text) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        Spanned spanned = Html.fromHtml(text);
        postprocess(context, spanned);
        return spanned;
        return postprocess(context, spanned);
    }

    /**
     * Converts HTML string to a {@link Spanned} text, adjusting formatting and using a custom
     * image getter.
     * image getter. Any extra new line characters at the end of the text will be trimmed.
     */
    public static CharSequence fromHtml(Context context, String text, ImageGetter imageGetter,
            TagHandler tagHandler) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        Spanned spanned = Html.fromHtml(text, imageGetter, tagHandler);
        postprocess(context, spanned);
        return spanned;
        return postprocess(context, Html.fromHtml(text, imageGetter, tagHandler));
    }

    /**
     * Replaces some spans with custom versions of those.
     * Replaces some spans with custom versions of those. Any extra new line characters at the end
     * of the text will be trimmed.
     */
    private static void postprocess(Context context, Spanned spanned) {
        if (!(spanned instanceof SpannableStringBuilder)) {
            return;
    @VisibleForTesting
    static Spanned postprocess(Context context, Spanned original) {
        if (original == null) {
            return null;
        }
        final int length = original.length();
        if (length == 0) {
            return original; // Bail early.
        }

        int length = spanned.length();
        // If it's a SpannableStringBuilder, just use it.  Otherwise, create a new
        // SpannableStringBuilder based on the passed Spanned.
        final SpannableStringBuilder builder;
        if (original instanceof SpannableStringBuilder) {
            builder = (SpannableStringBuilder) original;
        } else {
            builder = new SpannableStringBuilder(original);
        }

        SpannableStringBuilder builder = (SpannableStringBuilder)spanned;
        QuoteSpan[] quoteSpans = spanned.getSpans(0, length, QuoteSpan.class);
        final QuoteSpan[] quoteSpans = builder.getSpans(0, length, QuoteSpan.class);
        if (quoteSpans != null && quoteSpans.length != 0) {
            Resources resources = context.getResources();
            int color = resources.getColor(R.color.stream_item_stripe_color);
@@ -67,7 +78,7 @@ public class HtmlUtils {
            }
        }

        ImageSpan[] imageSpans = spanned.getSpans(0, length, ImageSpan.class);
        final ImageSpan[] imageSpans = builder.getSpans(0, length, ImageSpan.class);
        if (imageSpans != null) {
            for (int i = 0; i < imageSpans.length; i++) {
                ImageSpan span = imageSpans[i];
@@ -75,6 +86,25 @@ public class HtmlUtils {
                        ImageSpan.ALIGN_BASELINE));
            }
        }

        // Trim the trailing new line characters at the end of the text (which can be added
        // when HTML block quote tags are turned into new line characters).
        int end = length;
        for (int i = builder.length() - 1; i >= 0; i--) {
            if (builder.charAt(i) != '\n') {
                break;
            }
            end = i;
        }

        // If there's no trailing newlines, just return it.
        if (end == length) {
            return builder;
        }

        // Otherwise, Return a substring of the original {@link Spanned} text
        // from the start index (inclusive) to the end index (exclusive).
        return new SpannableStringBuilder(builder, 0, end);
    }

    /**
+122 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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 com.android.contacts.util;

import com.android.contacts.util.HtmlUtils.StreamItemQuoteSpan;

import android.graphics.drawable.ColorDrawable;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.style.ImageSpan;
import android.text.style.QuoteSpan;

/**
 * Tests for {@link HtmlUtils}.
 *
 * adb shell am instrument -w -e class com.android.contacts.util.HtmlUtilsTest \
       com.android.contacts.tests/android.test.InstrumentationTestRunner
 */
@SmallTest
public class HtmlUtilsTest extends AndroidTestCase {
    /**
     * Test for {@link HtmlUtils#postprocess} specifically about trimming newlines.
     */
    public void testPostProcess_trimNewLines() {
        checkTrimNewLines("", "");
        checkTrimNewLines("", "\n");
        checkTrimNewLines("", "\n\n");
        checkTrimNewLines("a", "a");
        checkTrimNewLines("abc", "abc");
        checkTrimNewLines("abc", "abc\n");
        checkTrimNewLines("abc", "abc\n\n\n");
        checkTrimNewLines("ab\nc", "ab\nc\n");

        assertNull(HtmlUtils.postprocess(getContext(), null));
    }

    private final void checkTrimNewLines(String expectedString, CharSequence text) {
        // Test with both SpannedString and SpannableStringBuilder.
        assertEquals(expectedString,
                HtmlUtils.postprocess(getContext(), new SpannedString(text)).toString());

        assertEquals(expectedString,
                HtmlUtils.postprocess(getContext(), new SpannableStringBuilder(text)).toString());
    }

    public void testPostProcess_with_newlines() {
        final SpannableStringBuilder builder = new SpannableStringBuilder("01234\n\n");

        setSpans(builder);

        // First test with a SpannableStringBuilder, as opposed to SpannedString
        checkPostProcess(HtmlUtils.postprocess(getContext(), builder));

        // Then pass a SpannedString, which is immutable, but the method should still work.
        checkPostProcess(HtmlUtils.postprocess(getContext(), new SpannedString(builder)));
    }

    /**
     * Same as {@link #testPostProcess_with_newlines}, but text has no newlines.
     * (The internal code path is slightly different.)
     */
    public void testPostProcess_no_newlines() {
        final SpannableStringBuilder builder = new SpannableStringBuilder("01234");

        setSpans(builder);

        // First test with a SpannableStringBuilder, as opposed to SpannedString
        checkPostProcess(HtmlUtils.postprocess(getContext(), builder));

        // Then pass a SpannedString, which is immutable, but the method should still work.
        checkPostProcess(HtmlUtils.postprocess(getContext(), new SpannedString(builder)));
    }

    private void setSpans(SpannableStringBuilder builder) {
        builder.setSpan(new ImageSpan(new ColorDrawable(), ImageSpan.ALIGN_BOTTOM), 0, 2, 0);
        builder.setSpan(new QuoteSpan(), 2, 4, 0);
        builder.setSpan(new CustomSpan(), 4, builder.length(), 0);
    }

    private void checkPostProcess(Spanned ret) {
        // Newlines should be trimmed.
        assertEquals("01234", ret.toString());

        // First, check the image span.
        // - Vertical alignment should be changed to ALIGN_BASELINE
        // - Drawable shouldn't be changed.
        ImageSpan[] imageSpans = ret.getSpans(0, ret.length(), ImageSpan.class);
        assertEquals(1, imageSpans.length);
        assertEquals(ImageSpan.ALIGN_BASELINE, imageSpans[0].getVerticalAlignment());
        assertEquals(ColorDrawable.class, imageSpans[0].getDrawable().getClass());

        // QuoteSpans should be replaced with StreamItemQuoteSpans.
        QuoteSpan[] quoteSpans = ret.getSpans(0, ret.length(), QuoteSpan.class);
        assertEquals(1, quoteSpans.length);
        assertEquals(StreamItemQuoteSpan.class, quoteSpans[0].getClass());

        // Other spans should be preserved.
        CustomSpan[] customSpans = ret.getSpans(0, ret.length(), CustomSpan.class);
        assertEquals(1, customSpans.length);
    }

    /** Custom span class used in {@link #testPostProcess} */
    private static class CustomSpan {
    }
}