Merge "Introduce MeasuredText related perf tests"
diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java
new file mode 100644
index 0000000..fc6302e
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.text;
+
+import static android.text.TextDirectionHeuristics.LTR;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.style.TextAppearanceSpan;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.CharBuffer;
+import java.util.Random;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MeasuredTextMemoryUsageTest {
+    private static final int WORD_LENGTH = 9;  // Random word has 9 characters.
+    private static final boolean NO_STYLE_TEXT = false;
+
+    private static TextPaint PAINT = new TextPaint();
+
+    private static int TRIAL_COUNT = 100;
+
+    public MeasuredTextMemoryUsageTest() {}
+
+    private TextPerfUtils mTextUtil = new TextPerfUtils();
+
+    @Before
+    public void setUp() {
+        mTextUtil.resetRandom(0 /* seed */);
+    }
+
+    private void reportMemoryUsage(int memoryUsage, String key) {
+        Bundle status = new Bundle();
+        status.putInt(key + "_median", memoryUsage);
+        InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+    }
+
+    private int median(int[] values) {
+        return values.length % 2 == 0 ?
+                (values[values.length / 2] + values[values.length / 2 - 1]) / 2:
+                values[values.length / 2];
+    }
+
+    @Test
+    public void testMemoryUsage_NoHyphenation() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                .build().getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation");
+    }
+
+    @Test
+    public void testMemoryUsage_Hyphenation() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .build().getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation");
+    }
+
+    @Test
+    public void testMemoryUsage_NoHyphenation_WidthOnly() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                .build(false /* width only */).getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation_WidthOnly");
+    }
+
+    @Test
+    public void testMemoryUsage_Hyphenatation_WidthOnly() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .build(false /* width only */).getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation_WidthOnly");
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java
new file mode 100644
index 0000000..98f2bd5
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.text;
+
+import static android.text.TextDirectionHeuristics.LTR;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.style.TextAppearanceSpan;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.CharBuffer;
+import java.util.Random;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MeasuredTextPerfTest {
+    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 MeasuredTextPerfTest() {}
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private TextPerfUtils mTextUtil = new TextPerfUtils();
+
+    @Before
+    public void setUp() {
+        mTextUtil.resetRandom(0 /* seed */);
+    }
+
+    @Test
+    public void testCreate_NoStyled_Hyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_NoStyled_NoHyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_NoStyled_Hyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(false /* width only */);
+        }
+    }
+
+    @Test
+    public void testCreate_NoStyled_NoHyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(false /* width only */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_Hyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_NoHyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_Hyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(false /* width only */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_NoHyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(false /* width only */);
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 682885b..bab2a85 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -43,74 +43,30 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class StaticLayoutPerfTest {
+    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 StaticLayoutPerfTest() {}
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    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 int PARA_LENGTH = 500;  // Number of characters in a paragraph.
-
-    private static final boolean NO_STYLE_TEXT = false;
-    private static final boolean STYLE_TEXT = true;
-
-    private Random mRandom;
-
-    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-    private static final int ALPHABET_LENGTH = ALPHABET.length();
-
-    private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000);
-    private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" };
-    private static final int[] STYLES = {
-            Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC
-    };
-
-    private final char[] mBuffer = new char[PARA_LENGTH];
-
-    private static TextPaint PAINT = new TextPaint();
-    private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();
-
-    private CharSequence generateRandomParagraph(int wordLen, boolean applyRandomStyle) {
-        for (int i = 0; i < PARA_LENGTH; i++) {
-            if (i % (wordLen + 1) == wordLen) {
-                mBuffer[i] = ' ';
-            } else {
-                mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH));
-            }
-        }
-
-        CharSequence cs = CharBuffer.wrap(mBuffer);
-        if (!applyRandomStyle) {
-            return cs;
-        }
-
-        SpannableStringBuilder ssb = new SpannableStringBuilder(cs);
-        for (int i = 0; i < ssb.length(); i += WORD_LENGTH) {
-            final int spanStart = i;
-            final int spanEnd = (i + WORD_LENGTH) > ssb.length() ? ssb.length() : i + WORD_LENGTH;
-
-            final TextAppearanceSpan span = new TextAppearanceSpan(
-                  FAMILIES[mRandom.nextInt(FAMILIES.length)],
-                  STYLES[mRandom.nextInt(STYLES.length)],
-                  24 + mRandom.nextInt(32),  // text size. min 24 max 56
-                  TEXT_COLOR, TEXT_COLOR);
-
-            ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-        }
-        return ssb;
-    }
+    private TextPerfUtils mTextUtil = new TextPerfUtils();
 
     @Before
     public void setUp() {
-        mRandom = new Random(0);
+        mTextUtil.resetRandom(0 /* seed */);
     }
 
     @Test
     public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+        final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
         while (state.keepRunning()) {
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
@@ -124,7 +80,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -139,7 +95,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -154,7 +110,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -169,7 +125,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -184,7 +140,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -200,7 +156,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                     .build();
@@ -219,7 +175,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                     .build();
@@ -238,7 +194,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                     .build();
@@ -257,7 +213,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                     .build();
@@ -276,7 +232,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                     .build();
@@ -292,7 +248,7 @@
     @Test
     public void testDraw_FixedText_NoStyled() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+        final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
@@ -311,7 +267,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -327,7 +283,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -343,7 +299,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -360,7 +316,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -378,7 +334,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -395,7 +351,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -412,7 +368,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -430,7 +386,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
diff --git a/apct-tests/perftests/core/src/android/text/TextPerfUtils.java b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java
new file mode 100644
index 0000000..dccb34b
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java
@@ -0,0 +1,93 @@
+/*
+ * 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.text;
+
+import static android.text.TextDirectionHeuristics.LTR;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.style.TextAppearanceSpan;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.CharBuffer;
+import java.util.Random;
+
+public class TextPerfUtils {
+
+    private static final int PARA_LENGTH = 500;  // Number of characters in a paragraph.
+
+    private Random mRandom = new Random(0);
+
+    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    private static final int ALPHABET_LENGTH = ALPHABET.length();
+
+    private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000);
+    private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" };
+    private static final int[] STYLES = {
+            Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC
+    };
+
+    private final char[] mBuffer = new char[PARA_LENGTH];
+
+    public void resetRandom(long seed) {
+        mRandom = new Random(seed);
+    }
+
+    public CharSequence nextRandomParagraph(int wordLen, boolean applyRandomStyle) {
+        for (int i = 0; i < PARA_LENGTH; i++) {
+            if (i % (wordLen + 1) == wordLen) {
+                mBuffer[i] = ' ';
+            } else {
+                mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH));
+            }
+        }
+
+        CharSequence cs = CharBuffer.wrap(mBuffer);
+        if (!applyRandomStyle) {
+            return cs;
+        }
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder(cs);
+        for (int i = 0; i < ssb.length(); i += wordLen) {
+            final int spanStart = i;
+            final int spanEnd = (i + wordLen) > ssb.length() ? ssb.length() : i + wordLen;
+
+            final TextAppearanceSpan span = new TextAppearanceSpan(
+                  FAMILIES[mRandom.nextInt(FAMILIES.length)],
+                  STYLES[mRandom.nextInt(STYLES.length)],
+                  24 + mRandom.nextInt(32),  // text size. min 24 max 56
+                  TEXT_COLOR, TEXT_COLOR);
+
+            ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        return ssb;
+    }
+}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 45fbf6f..aafcf44 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -672,6 +672,13 @@
         return width;
     }
 
+    /**
+     * This only works if the MeasuredParagraph is computed with buildForStaticLayout.
+     */
+    @IntRange(from = 0) int getMemoryUsage() {
+        return nGetMemoryUsage(mNativePtr);
+    }
+
     private static native /* Non Zero */ long nInitBuilder();
 
     /**
@@ -718,4 +725,7 @@
 
     @CriticalNative
     private static native /* Non Zero */ long nGetReleaseFunc();
+
+    @CriticalNative
+    private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
 }
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ff23395..bb7a9e0 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -333,6 +333,20 @@
         return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
     }
 
+    /**
+     * Returns the size of native MeasuredText memory usage
+     *
+     * Note that this may not be aculate. Must be used only for testing purposes.
+     * @hide
+     */
+    public int getMemoryUsage() {
+        int r = 0;
+        for (int i = 0; i < getParagraphCount(); ++i) {
+            r += getMeasuredParagraph(i).getMemoryUsage();
+        }
+        return r;
+    }
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Spanned overrides
     //
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index f0e449d..dddddbb 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -115,6 +115,10 @@
     return toJLong(&releaseMeasuredParagraph);
 }
 
+static jint nGetMemoryUsage(jlong ptr) {
+    return static_cast<jint>(toMeasuredParagraph(ptr)->getMemoryUsage());
+}
+
 static const JNINativeMethod gMethods[] = {
     // MeasuredParagraphBuilder native functions.
     {"nInitBuilder", "()J", (void*) nInitBuilder},
@@ -126,6 +130,7 @@
     // MeasuredParagraph native functions.
     {"nGetWidth", "(JII)F", (void*) nGetWidth},  // Critical Natives
     {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},  // Critical Natives
+    {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage},  // Critical Native
 };
 
 int register_android_text_MeasuredParagraph(JNIEnv* env) {