Loading apct-tests/perftests/core/src/android/text/TextViewCursorAnchorInfoPerfTest.java 0 → 100644 +133 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2022 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 android.app.Activity; import android.content.Context; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.PerfTestActivity; import android.platform.test.annotations.LargeTest; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.CursorAnchorInfo; import android.widget.TextView; import androidx.test.rule.ActivityTestRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.Arrays; import java.util.Collection; @LargeTest @RunWith(Parameterized.class) public class TextViewCursorAnchorInfoPerfTest { @Parameterized.Parameters(name = "mTextLength ({0}))") public static Collection<Integer> data() { return Arrays.asList(100, 300, 1000, 3000, 10000); } private static final int WORD_LENGTH = 9; // Random word has 9 characters. private static final int WORDS_IN_LINE = 12; // Roughly, 12 words in a line. private static final TextPaint PAINT = new TextPaint(); // The width is an estimation. There might be lines with less or more words. private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); private static final int TEXT_HEIGHT = 1024; @Parameterized.Parameter(0) public int mTextLength; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @Rule public ActivityTestRule<PerfTestActivity> mActivityTestRule = new ActivityTestRule<>( PerfTestActivity.class); private TextPerfUtils mTextUtil = new TextPerfUtils(); private PerfTextView mTextView; @Before public void setUp() { mTextUtil.resetRandom(0 /* seed */); } public void setUpTextView() { Activity activity = mActivityTestRule.getActivity(); mTextView = new PerfTextView(activity); mTextView.setHeight(TEXT_HEIGHT); mTextView.setWidth(TEXT_WIDTH); final ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(TEXT_WIDTH, TEXT_HEIGHT); mTextView.setLayoutParams(p); activity.setContentView(mTextView); mTextView.setText(mTextUtil.nextRandomParagraph(WORD_LENGTH, mTextLength)); mTextView.invalidate(); mTextView.mySetFrame(TEXT_WIDTH, TEXT_HEIGHT); mTextView.measure(View.MeasureSpec.makeMeasureSpec(TEXT_WIDTH, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(TEXT_HEIGHT, View.MeasureSpec.EXACTLY)); mTextView.layout(0, 0, TEXT_WIDTH, TEXT_HEIGHT); } @Test public void testPopulateCharacterBounds() throws Throwable { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); mActivityTestRule.runOnUiThread(() -> { setUpTextView(); while (state.keepRunning()) { final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); mTextView.populateCharacterBounds(builder, 0, mTextLength, 0f, 0f); } }); } private static class PerfTextView extends TextView { PerfTextView(Context context) { super(context); } PerfTextView(Context context, AttributeSet attrs) { super(context, attrs); } PerfTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } PerfTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } void mySetFrame(int width, int height) { super.setFrame(0, 0, width, height); } } } core/java/android/widget/TextView.java +10 −13 Original line number Original line Diff line number Diff line Loading @@ -12493,6 +12493,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float viewportToContentVerticalOffset) { float viewportToContentVerticalOffset) { final int minLine = mLayout.getLineForOffset(startIndex); final int minLine = mLayout.getLineForOffset(startIndex); final int maxLine = mLayout.getLineForOffset(endIndex - 1); final int maxLine = mLayout.getLineForOffset(endIndex - 1); final Rect rect = new Rect(); getLocalVisibleRect(rect); final RectF visibleRect = new RectF(rect); for (int line = minLine; line <= maxLine; ++line) { for (int line = minLine; line <= maxLine; ++line) { final int lineStart = mLayout.getLineStart(line); final int lineStart = mLayout.getLineStart(line); final int lineEnd = mLayout.getLineEnd(line); final int lineEnd = mLayout.getLineEnd(line); Loading @@ -12507,37 +12510,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener for (int offset = offsetStart; offset < offsetEnd; ++offset) { for (int offset = offsetStart; offset < offsetEnd; ++offset) { final float charWidth = widths[offset - offsetStart]; final float charWidth = widths[offset - offsetStart]; final boolean isRtl = mLayout.isRtlCharAt(offset); final boolean isRtl = mLayout.isRtlCharAt(offset); final float primary = mLayout.getPrimaryHorizontal(offset); final float secondary = mLayout.getSecondaryHorizontal(offset); // TODO: This doesn't work perfectly for text with custom styles and // TODO: This doesn't work perfectly for text with custom styles and // TAB chars. // TAB chars. final float left; final float left; final float right; if (ltrLine) { if (ltrLine) { if (isRtl) { if (isRtl) { left = secondary - charWidth; left = mLayout.getSecondaryHorizontal(offset) - charWidth; right = secondary; } else { } else { left = primary; left = mLayout.getPrimaryHorizontal(offset); right = primary + charWidth; } } } else { } else { if (!isRtl) { if (!isRtl) { left = secondary; left = mLayout.getSecondaryHorizontal(offset); right = secondary + charWidth; } else { } else { left = primary - charWidth; left = mLayout.getPrimaryHorizontal(offset) - charWidth; right = primary; } } } } final float right = left + charWidth; // TODO: Check top-right and bottom-left as well. // TODO: Check top-right and bottom-left as well. final float localLeft = left + viewportToContentHorizontalOffset; final float localLeft = left + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); final boolean isTopLeftVisible = visibleRect.contains(localLeft, localTop); final boolean isBottomRightVisible = final boolean isBottomRightVisible = isPositionVisible(localRight, localBottom); visibleRect.contains(localRight, localBottom); int characterBoundsFlags = 0; int characterBoundsFlags = 0; if (isTopLeftVisible || isBottomRightVisible) { if (isTopLeftVisible || isBottomRightVisible) { characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; Loading Loading
apct-tests/perftests/core/src/android/text/TextViewCursorAnchorInfoPerfTest.java 0 → 100644 +133 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2022 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 android.app.Activity; import android.content.Context; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.PerfTestActivity; import android.platform.test.annotations.LargeTest; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.CursorAnchorInfo; import android.widget.TextView; import androidx.test.rule.ActivityTestRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.Arrays; import java.util.Collection; @LargeTest @RunWith(Parameterized.class) public class TextViewCursorAnchorInfoPerfTest { @Parameterized.Parameters(name = "mTextLength ({0}))") public static Collection<Integer> data() { return Arrays.asList(100, 300, 1000, 3000, 10000); } private static final int WORD_LENGTH = 9; // Random word has 9 characters. private static final int WORDS_IN_LINE = 12; // Roughly, 12 words in a line. private static final TextPaint PAINT = new TextPaint(); // The width is an estimation. There might be lines with less or more words. private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); private static final int TEXT_HEIGHT = 1024; @Parameterized.Parameter(0) public int mTextLength; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @Rule public ActivityTestRule<PerfTestActivity> mActivityTestRule = new ActivityTestRule<>( PerfTestActivity.class); private TextPerfUtils mTextUtil = new TextPerfUtils(); private PerfTextView mTextView; @Before public void setUp() { mTextUtil.resetRandom(0 /* seed */); } public void setUpTextView() { Activity activity = mActivityTestRule.getActivity(); mTextView = new PerfTextView(activity); mTextView.setHeight(TEXT_HEIGHT); mTextView.setWidth(TEXT_WIDTH); final ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(TEXT_WIDTH, TEXT_HEIGHT); mTextView.setLayoutParams(p); activity.setContentView(mTextView); mTextView.setText(mTextUtil.nextRandomParagraph(WORD_LENGTH, mTextLength)); mTextView.invalidate(); mTextView.mySetFrame(TEXT_WIDTH, TEXT_HEIGHT); mTextView.measure(View.MeasureSpec.makeMeasureSpec(TEXT_WIDTH, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(TEXT_HEIGHT, View.MeasureSpec.EXACTLY)); mTextView.layout(0, 0, TEXT_WIDTH, TEXT_HEIGHT); } @Test public void testPopulateCharacterBounds() throws Throwable { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); mActivityTestRule.runOnUiThread(() -> { setUpTextView(); while (state.keepRunning()) { final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); mTextView.populateCharacterBounds(builder, 0, mTextLength, 0f, 0f); } }); } private static class PerfTextView extends TextView { PerfTextView(Context context) { super(context); } PerfTextView(Context context, AttributeSet attrs) { super(context, attrs); } PerfTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } PerfTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } void mySetFrame(int width, int height) { super.setFrame(0, 0, width, height); } } }
core/java/android/widget/TextView.java +10 −13 Original line number Original line Diff line number Diff line Loading @@ -12493,6 +12493,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float viewportToContentVerticalOffset) { float viewportToContentVerticalOffset) { final int minLine = mLayout.getLineForOffset(startIndex); final int minLine = mLayout.getLineForOffset(startIndex); final int maxLine = mLayout.getLineForOffset(endIndex - 1); final int maxLine = mLayout.getLineForOffset(endIndex - 1); final Rect rect = new Rect(); getLocalVisibleRect(rect); final RectF visibleRect = new RectF(rect); for (int line = minLine; line <= maxLine; ++line) { for (int line = minLine; line <= maxLine; ++line) { final int lineStart = mLayout.getLineStart(line); final int lineStart = mLayout.getLineStart(line); final int lineEnd = mLayout.getLineEnd(line); final int lineEnd = mLayout.getLineEnd(line); Loading @@ -12507,37 +12510,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener for (int offset = offsetStart; offset < offsetEnd; ++offset) { for (int offset = offsetStart; offset < offsetEnd; ++offset) { final float charWidth = widths[offset - offsetStart]; final float charWidth = widths[offset - offsetStart]; final boolean isRtl = mLayout.isRtlCharAt(offset); final boolean isRtl = mLayout.isRtlCharAt(offset); final float primary = mLayout.getPrimaryHorizontal(offset); final float secondary = mLayout.getSecondaryHorizontal(offset); // TODO: This doesn't work perfectly for text with custom styles and // TODO: This doesn't work perfectly for text with custom styles and // TAB chars. // TAB chars. final float left; final float left; final float right; if (ltrLine) { if (ltrLine) { if (isRtl) { if (isRtl) { left = secondary - charWidth; left = mLayout.getSecondaryHorizontal(offset) - charWidth; right = secondary; } else { } else { left = primary; left = mLayout.getPrimaryHorizontal(offset); right = primary + charWidth; } } } else { } else { if (!isRtl) { if (!isRtl) { left = secondary; left = mLayout.getSecondaryHorizontal(offset); right = secondary + charWidth; } else { } else { left = primary - charWidth; left = mLayout.getPrimaryHorizontal(offset) - charWidth; right = primary; } } } } final float right = left + charWidth; // TODO: Check top-right and bottom-left as well. // TODO: Check top-right and bottom-left as well. final float localLeft = left + viewportToContentHorizontalOffset; final float localLeft = left + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); final boolean isTopLeftVisible = visibleRect.contains(localLeft, localTop); final boolean isBottomRightVisible = final boolean isBottomRightVisible = isPositionVisible(localRight, localBottom); visibleRect.contains(localRight, localBottom); int characterBoundsFlags = 0; int characterBoundsFlags = 0; if (isTopLeftVisible || isBottomRightVisible) { if (isTopLeftVisible || isBottomRightVisible) { characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; Loading