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

Commit 0efaf208 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce new perf test for TextView with precomputed text" into pi-dev

parents 4e532f59 d15f04ef
Loading
Loading
Loading
Loading
+370 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.widget;

import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.UNSPECIFIED;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.graphics.Canvas;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.PrecomputedText;
import android.text.Layout;
import android.text.BoringLayout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.TextAppearanceSpan;
import android.view.LayoutInflater;
import android.text.TextPerfUtils;
import android.view.View.MeasureSpec;
import android.view.DisplayListCanvas;
import android.view.RenderNode;

import com.android.perftests.core.R;

import java.util.Random;
import java.util.Locale;

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

import static org.junit.Assert.assertTrue;

import static android.widget.TextView.UNKNOWN_BORING;

@LargeTest
@RunWith(AndroidJUnit4.class)
public class TextViewPrecomputedTextPerfTest {
    private static final int WORD_LENGTH = 9;  // Random word has 9 characters.
    private static final int WORDS_IN_LINE = 8;  // Roughly, 8 words in a line.
    private static final boolean NO_STYLE_TEXT = false;
    private static final boolean STYLE_TEXT = true;

    private static TextPaint PAINT = new TextPaint();
    private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();

    public TextViewPrecomputedTextPerfTest() {}

    private static class TestableTextView extends TextView {
        public TestableTextView(Context ctx) {
            super(ctx);
        }

        public void onMeasure(int w, int h) {
            super.onMeasure(w, h);
        }

        public void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    }

    @Rule
    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

    private TextPerfUtils mTextUtil = new TextPerfUtils();

    @Before
    public void setUp() {
        mTextUtil.resetRandom(0 /* seed */);
    }

    private static Context getContext() {
        return InstrumentationRegistry.getTargetContext();
    }

    @Test
    public void testNewLayout_RandomText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BoringLayout.Metrics metrics = new BoringLayout.Metrics();
        while (state.keepRunning()) {
            state.pauseTiming();
            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
            final TextView textView = new TextView(getContext());
            textView.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED);
            textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
            textView.setText(text);
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.makeNewLayout(TEXT_WIDTH, TEXT_WIDTH, UNKNOWN_BORING, UNKNOWN_BORING,
                TEXT_WIDTH, false);
        }
    }

    @Test
    public void testNewLayout_PrecomputedText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BoringLayout.Metrics metrics = new BoringLayout.Metrics();
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TextView textView = new TextView(getContext());
            textView.setTextMetricsParams(params);
            textView.setText(text);
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.makeNewLayout(TEXT_WIDTH, TEXT_WIDTH, UNKNOWN_BORING, UNKNOWN_BORING,
                TEXT_WIDTH, false);
        }
    }

    @Test
    public void testNewLayout_PrecomputedText_Selectable() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BoringLayout.Metrics metrics = new BoringLayout.Metrics();
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TextView textView = new TextView(getContext());
            textView.setTextIsSelectable(true);
            textView.setTextMetricsParams(params);
            textView.setText(text);
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.makeNewLayout(TEXT_WIDTH, TEXT_WIDTH, UNKNOWN_BORING, UNKNOWN_BORING,
                TEXT_WIDTH, false);
        }
    }

    @Test
    public void testSetText_RandomText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BoringLayout.Metrics metrics = new BoringLayout.Metrics();
        while (state.keepRunning()) {
            state.pauseTiming();
            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
            final TextView textView = new TextView(getContext());
            textView.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED);
            textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.setText(text);
        }
    }

    @Test
    public void testSetText_PrecomputedText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BoringLayout.Metrics metrics = new BoringLayout.Metrics();
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TextView textView = new TextView(getContext());
            textView.setTextMetricsParams(params);
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.setText(text);
        }
    }

    @Test
    public void testSetText_PrecomputedText_Selectable() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BoringLayout.Metrics metrics = new BoringLayout.Metrics();
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TextView textView = new TextView(getContext());
            textView.setTextIsSelectable(true);
            textView.setTextMetricsParams(params);
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.setText(text);
        }
    }

    @Test
    public void testOnMeasure_RandomText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        int width = MeasureSpec.makeMeasureSpec(MeasureSpec.AT_MOST, TEXT_WIDTH);
        int height = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
        while (state.keepRunning()) {
            state.pauseTiming();
            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
            final TestableTextView textView = new TestableTextView(getContext());
            textView.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED);
            textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
            textView.setText(text);
            textView.nullLayouts();
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.onMeasure(width, height);
        }
    }

    @Test
    public void testOnMeasure_PrecomputedText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        int width = MeasureSpec.makeMeasureSpec(MeasureSpec.AT_MOST, TEXT_WIDTH);
        int height = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TestableTextView textView = new TestableTextView(getContext());
            textView.setTextMetricsParams(params);
            textView.setText(text);
            textView.nullLayouts();
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.onMeasure(width, height);
        }
    }

    @Test
    public void testOnMeasure_PrecomputedText_Selectable() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        int width = MeasureSpec.makeMeasureSpec(MeasureSpec.AT_MOST, TEXT_WIDTH);
        int height = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TestableTextView textView = new TestableTextView(getContext());
            textView.setTextIsSelectable(true);
            textView.setTextMetricsParams(params);
            textView.setText(text);
            textView.nullLayouts();
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.onMeasure(width, height);
        }
    }

    @Test
    public void testOnDraw_RandomText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        int width = MeasureSpec.makeMeasureSpec(MeasureSpec.AT_MOST, TEXT_WIDTH);
        int height = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
        final RenderNode node = RenderNode.create("benchmark", null);
        while (state.keepRunning()) {
            state.pauseTiming();
            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
            final TestableTextView textView = new TestableTextView(getContext());
            textView.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED);
            textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
            textView.setText(text);
            textView.nullLayouts();
            textView.onMeasure(width, height);
            final DisplayListCanvas c = node.start(
                textView.getMeasuredWidth(), textView.getMeasuredHeight());
            textView.nullLayouts();
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.onDraw(c);
        }
    }

    @Test
    public void testOnDraw_PrecomputedText() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        int width = MeasureSpec.makeMeasureSpec(MeasureSpec.AT_MOST, TEXT_WIDTH);
        int height = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
        final RenderNode node = RenderNode.create("benchmark", null);
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TestableTextView textView = new TestableTextView(getContext());
            textView.setTextMetricsParams(params);
            textView.setText(text);
            textView.nullLayouts();
            textView.onMeasure(width, height);
            final DisplayListCanvas c = node.start(
                textView.getMeasuredWidth(), textView.getMeasuredHeight());
            textView.nullLayouts();
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.onDraw(c);
        }
    }

    @Test
    public void testOnDraw_PrecomputedText_Selectable() {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        int width = MeasureSpec.makeMeasureSpec(MeasureSpec.AT_MOST, TEXT_WIDTH);
        int height = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
        final RenderNode node = RenderNode.create("benchmark", null);
        while (state.keepRunning()) {
            state.pauseTiming();
            final PrecomputedText.Params params = new PrecomputedText.Params.Builder(PAINT)
                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
            final CharSequence text = PrecomputedText.create(
                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), params);
            final TestableTextView textView = new TestableTextView(getContext());
            textView.setTextIsSelectable(true);
            textView.setTextMetricsParams(params);
            textView.setText(text);
            textView.nullLayouts();
            textView.onMeasure(width, height);
            final DisplayListCanvas c = node.start(
                textView.getMeasuredWidth(), textView.getMeasuredHeight());
            textView.nullLayouts();
            Canvas.freeTextLayoutCaches();
            state.resumeTiming();

            textView.onDraw(c);
        }
    }
}
+8 −3
Original line number Diff line number Diff line
@@ -8084,7 +8084,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return false;
    }

    private void nullLayouts() {
    /** @hide */
    @VisibleForTesting
    public void nullLayouts() {
        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
            mSavedLayout = (BoringLayout) mLayout;
        }
@@ -8178,7 +8180,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * not the full view width with padding.
     * {@hide}
     */
    protected void makeNewLayout(int wantWidth, int hintWidth,
    @VisibleForTesting
    public void makeNewLayout(int wantWidth, int hintWidth,
                                 BoringLayout.Metrics boring,
                                 BoringLayout.Metrics hintBoring,
                                 int ellipsisWidth, boolean bringIntoView) {
@@ -8468,7 +8471,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return mIncludePad;
    }

    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
    /** @hide */
    @VisibleForTesting
    public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+4 −8
Original line number Diff line number Diff line
@@ -16,12 +16,10 @@

package android.widget.layout.linear;

import junit.framework.Assert;

import android.widget.EditText;
import android.content.Context;
import android.util.AttributeSet;
import android.text.BoringLayout;
import android.util.AttributeSet;
import android.widget.EditText;


/**
@@ -50,10 +48,8 @@ public class ExceptionTextView extends EditText {
    }

    @Override
    protected void makeNewLayout(int w, int hintWidth,
                                 BoringLayout.Metrics boring,
                                 BoringLayout.Metrics hintMetrics,
                                 int ellipsizedWidth, boolean bringIntoView) {
    public void makeNewLayout(int w, int hintWidth, BoringLayout.Metrics boring,
            BoringLayout.Metrics hintMetrics, int ellipsizedWidth, boolean bringIntoView) {
        if (w < 0) {
            mFailed = true;
            w = 100;