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

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

Merge "Add highlight APIs into TextView and Layout"

parents 1221a9c1 16ad1f7b
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -45670,6 +45670,19 @@ package android.text {
    method public int previousStartBoundary(@IntRange(from=0) int);
  }
  public class Highlights {
    method @NonNull public android.graphics.Paint getPaint(int);
    method @NonNull public int[] getRanges(int);
    method public int getSize();
  }
  public static final class Highlights.Builder {
    ctor public Highlights.Builder();
    method @NonNull public android.text.Highlights.Builder addRange(@NonNull android.graphics.Paint, int, int);
    method @NonNull public android.text.Highlights.Builder addRanges(@NonNull android.graphics.Paint, @NonNull int...);
    method @NonNull public android.text.Highlights build();
  }
  public class Html {
    method public static String escapeHtml(CharSequence);
    method @Deprecated public static android.text.Spanned fromHtml(String);
@@ -45761,6 +45774,9 @@ package android.text {
    ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
    method public void draw(android.graphics.Canvas);
    method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
    method public void draw(@NonNull android.graphics.Canvas, @Nullable java.util.List<android.graphics.Path>, @Nullable java.util.List<android.graphics.Paint>, @Nullable android.graphics.Path, @Nullable android.graphics.Paint, int);
    method public void drawBackground(@NonNull android.graphics.Canvas);
    method public void drawText(@NonNull android.graphics.Canvas);
    method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int);
    method public final android.text.Layout.Alignment getAlignment();
    method public abstract int getBottomPadding();
@@ -58574,6 +58590,7 @@ package android.widget {
    method public boolean getFreezesText();
    method public int getGravity();
    method @ColorInt public int getHighlightColor();
    method @Nullable public android.text.Highlights getHighlights();
    method public CharSequence getHint();
    method public final android.content.res.ColorStateList getHintTextColors();
    method public int getHyphenationFrequency();
@@ -58703,6 +58720,7 @@ package android.widget {
    method public void setGravity(int);
    method public void setHeight(int);
    method public void setHighlightColor(@ColorInt int);
    method public void setHighlights(@Nullable android.text.Highlights);
    method public final void setHint(CharSequence);
    method public final void setHint(@StringRes int);
    method public final void setHintTextColor(@ColorInt int);
+148 −0
Original line number 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.graphics.Paint;
import android.util.Pair;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * A class that represents of the highlight of the text.
 */
public class Highlights {
    private final List<Pair<Paint, int[]>> mHighlights;

    private Highlights(List<Pair<Paint, int[]>> highlights) {
        mHighlights = highlights;
    }

    /**
     * Returns a number of highlight.
     *
     * @return a number of highlight.
     *
     * @see Builder#addRange(Paint, int, int)
     * @see Builder#addRanges(Paint, int...)
     */
    public @IntRange(from = 0) int getSize() {
        return mHighlights.size();
    }

    /**
     * Returns a paint used for the i-th highlight.
     *
     * @param index an index of the highlight. Must be between 0 and {@link #getSize()}
     * @return a paint object
     *
     * @see Builder#addRange(Paint, int, int)
     * @see Builder#addRanges(Paint, int...)
     */
    public @NonNull Paint getPaint(@IntRange(from = 0) int index) {
        return mHighlights.get(index).first;
    }

    /**
     * Returns ranges of the i-th highlight.
     *
     * Ranges are represented of flattened inclusive start and exclusive end integers array. The
     * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
     * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
     * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
     * [1, 2, 3, 4].
     *
     * @param index an index of the highlight. Must be between 0 and {@link #getSize()}
     * @return a paint object
     *
     * @see Builder#addRange(Paint, int, int)
     * @see Builder#addRanges(Paint, int...)
     */
    public @NonNull int[] getRanges(int index) {
        return mHighlights.get(index).second;
    }

    /**
     * A builder for the Highlights.
     */
    public static final class Builder {
        private final List<Pair<Paint, int[]>> mHighlights = new ArrayList<>();

        /**
         * Add single range highlight.
         *
         * @param paint a paint object used for drawing highlight path.
         * @param start an inclusive offset of the text.
         * @param end an exclusive offset of the text.
         * @return this builder instance.
         */
        public @NonNull Builder addRange(@NonNull Paint paint, @IntRange(from = 0) int start,
                @IntRange(from = 0) int end) {
            if (start > end) {
                throw new IllegalArgumentException("start must not be larger than end: "
                        + start + ", " + end);
            }
            Objects.requireNonNull(paint);

            int[] range = new int[] {start, end};
            mHighlights.add(new Pair<>(paint, range));
            return this;
        }

        /**
         * Add multiple ranges highlight.
         *
         * @param paint a paint object used for drawing highlight path.
         * @param ranges a flatten ranges. The {@code 2 * i}-th element is an inclusive start offset
         *              of the {@code i}-th character. The {@code 2 * i + 1}-th element is an
         *              exclusive end offset of the {@code i}-th character.
         * @return this builder instance.
         */
        public @NonNull Builder addRanges(@NonNull Paint paint, @NonNull int... ranges) {
            if (ranges.length % 2 == 1) {
                throw new IllegalArgumentException(
                        "Flatten ranges must have even numbered elements");
            }
            for (int j = 0; j < ranges.length / 2; ++j) {
                int start = ranges[j * 2];
                int end = ranges[j * 2 + 1];
                if (start > end) {
                    throw new IllegalArgumentException(
                            "Reverse range found in the flatten range: " + Arrays.toString(
                                    ranges));
                }
            }
            Objects.requireNonNull(paint);
            mHighlights.add(new Pair<>(paint, ranges));
            return this;
        }

        /**
         * Build a new Highlights instance.
         *
         * @return a new Highlights instance.
         */
        public @NonNull Highlights build() {
            return new Highlights(mHighlights);
        }
    }
}
+133 −16
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import com.android.internal.util.GrowingArrayUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;

/**
 * A base class that manages text layout in visual elements on
@@ -347,9 +348,13 @@ public abstract class Layout {

    /**
     * Draw this Layout on the specified Canvas.
     *
     * This API draws background first, then draws text on top of it.
     *
     * @see #draw(Canvas, List, List, Path, Paint, int)
     */
    public void draw(Canvas c) {
        draw(c, null, null, 0);
        draw(c, (Path) null, (Paint) null, 0);
    }

    /**
@@ -357,23 +362,142 @@ public abstract class Layout {
     * between the background and the text.
     *
     * @param canvas the canvas
     * @param highlight the path of the highlight or cursor; can be null
     * @param highlightPaint the paint for the highlight
     * @param selectionHighlight the path of the selection highlight or cursor; can be null
     * @param selectionHighlightPaint the paint for the selection highlight
     * @param cursorOffsetVertical the amount to temporarily translate the
     *        canvas while rendering the highlight
     *
     * @see #draw(Canvas, List, List, Path, Paint, int)
     */
    public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
    public void draw(
            Canvas canvas, Path selectionHighlight,
            Paint selectionHighlightPaint, int cursorOffsetVertical) {
        draw(canvas, null, null, selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
    }

    /**
     * Draw this layout on the specified canvas.
     *
     * This API draws background first, then draws highlight paths on top of it, then draws
     * selection or cursor, then finally draws text on top of it.
     *
     * @see #drawBackground(Canvas)
     * @see #drawText(Canvas)
     *
     * @param canvas the canvas
     * @param highlightPaths the path of the highlights. The highlightPaths and highlightPaints must
     *                      have the same length and aligned in the same order. For example, the
     *                      paint of the n-th of the highlightPaths should be stored at the n-th of
     *                      highlightPaints.
     * @param highlightPaints the paints for the highlights. The highlightPaths and highlightPaints
     *                        must have the same length and aligned in the same order. For example,
     *                        the paint of the n-th of the highlightPaths should be stored at the
     *                        n-th of highlightPaints.
     * @param selectionPath the selection or cursor path
     * @param selectionPaint the paint for the selection or cursor.
     * @param cursorOffsetVertical the amount to temporarily translate the canvas while rendering
     *                            the highlight
     */
    public void draw(@NonNull Canvas canvas,
            @Nullable List<Path> highlightPaths,
            @Nullable List<Paint> highlightPaints,
            @Nullable Path selectionPath,
            @Nullable Paint selectionPaint,
            int cursorOffsetVertical) {
        final long lineRange = getLineRangeForDraw(canvas);
        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
        if (lastLine < 0) return;

        drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
                firstLine, lastLine);
        drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
                cursorOffsetVertical, firstLine, lastLine);
        drawText(canvas, firstLine, lastLine);
    }

    /**
     * Draw text part of this layout.
     *
     * Different from {@link #draw(Canvas, List, List, Path, Paint, int)} API, this API only draws
     * text part, not drawing highlights, selections, or backgrounds.
     *
     * @see #draw(Canvas, List, List, Path, Paint, int)
     * @see #drawBackground(Canvas)
     *
     * @param canvas the canvas
     */
    public void drawText(@NonNull Canvas canvas) {
        final long lineRange = getLineRangeForDraw(canvas);
        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
        if (lastLine < 0) return;
        drawText(canvas, firstLine, lastLine);
    }

    /**
     * Draw background of this layout.
     *
     * Different from {@link #draw(Canvas, List, List, Path, Paint, int)} API, this API only draws
     * background, not drawing text, highlights or selections. The background here is drawn by
     * {@link LineBackgroundSpan} attached to the text.
     *
     * @see #draw(Canvas, List, List, Path, Paint, int)
     * @see #drawText(Canvas)
     *
     * @param canvas the canvas
     */
    public void drawBackground(@NonNull Canvas canvas) {
        final long lineRange = getLineRangeForDraw(canvas);
        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
        if (lastLine < 0) return;
        drawBackground(canvas, firstLine, lastLine);
    }

    /**
     * @hide public for Editor.java
     */
    public void drawWithoutText(
            @NonNull Canvas canvas,
            @Nullable List<Path> highlightPaths,
            @Nullable List<Paint> highlightPaints,
            @Nullable Path selectionPath,
            @Nullable Paint selectionPaint,
            int cursorOffsetVertical,
            int firstLine,
            int lastLine) {
        drawBackground(canvas, firstLine, lastLine);
        if (highlightPaths == null && highlightPaints == null) {
            return;
        }
        if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
        try {
            if (highlightPaths != null) {
                if (highlightPaints == null) {
                    throw new IllegalArgumentException(
                            "if highlight is specified, highlightPaint must be specified.");
                }
                if (highlightPaints.size() != highlightPaths.size()) {
                    throw new IllegalArgumentException(
                            "The highlight path size is different from the size of highlight"
                                    + " paints");
                }
                for (int i = 0; i < highlightPaths.size(); ++i) {
                    final Path highlight = highlightPaths.get(i);
                    final Paint highlightPaint = highlightPaints.get(i);
                    if (highlight != null) {
                        canvas.drawPath(highlight, highlightPaint);
                    }
                }
            }

            if (selectionPath != null) {
                canvas.drawPath(selectionPath, selectionPaint);
            }
        } finally {
            if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
        }
    }

    private boolean isJustificationRequired(int lineNum) {
        if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false;
        final int lineEnd = getLineEnd(lineNum);
@@ -635,8 +759,9 @@ public abstract class Layout {
     * @hide
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
            int cursorOffsetVertical, int firstLine, int lastLine) {
    public void drawBackground(
            @NonNull Canvas canvas,
            int firstLine, int lastLine) {
        // First, draw LineBackgroundSpans.
        // LineBackgroundSpans know nothing about the alignment, margins, or
        // direction of the layout or line.  XXX: Should they?
@@ -700,14 +825,6 @@ public abstract class Layout {
            }
            mLineBackgroundSpans.recycle();
        }

        // There can be a highlight even without spans if we are drawing
        // a non-spanned transformation of a spanned editing buffer.
        if (highlight != null) {
            if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
            canvas.drawPath(highlight, highlightPaint);
            if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
        }
    }

    /**
+23 −16
Original line number Diff line number Diff line
@@ -2054,7 +2054,10 @@ public class Editor {
        }
    }

    void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint,
    void onDraw(Canvas canvas, Layout layout,
            List<Path> highlightPaths,
            List<Paint> highlightPaints,
            Path selectionHighlight, Paint selectionHighlightPaint,
            int cursorOffsetVertical) {
        final int selectionStart = mTextView.getSelectionStart();
        final int selectionEnd = mTextView.getSelectionEnd();
@@ -2078,37 +2081,40 @@ public class Editor {
            mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
        }

        if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
        if (selectionHighlight != null && selectionStart == selectionEnd
                && mDrawableForCursor != null) {
            drawCursor(canvas, cursorOffsetVertical);
            // Rely on the drawable entirely, do not draw the cursor line.
            // Has to be done after the IMM related code above which relies on the highlight.
            highlight = null;
            selectionHighlight = null;
        }

        if (mSelectionActionModeHelper != null) {
            mSelectionActionModeHelper.onDraw(canvas);
            if (mSelectionActionModeHelper.isDrawingHighlight()) {
                highlight = null;
                selectionHighlight = null;
            }
        }

        if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
            drawHardwareAccelerated(canvas, layout, highlight, highlightPaint,
                    cursorOffsetVertical);
            drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
                    selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
            layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
                    selectionHighlightPaint, cursorOffsetVertical);
        }
    }

    private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
            Paint highlightPaint, int cursorOffsetVertical) {
    private void drawHardwareAccelerated(Canvas canvas, Layout layout,
            List<Path> highlightPaths, List<Paint> highlightPaints,
            Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
        final long lineRange = layout.getLineRangeForDraw(canvas);
        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
        if (lastLine < 0) return;

        layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
                firstLine, lastLine);
        layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
                selectionHighlightPaint, cursorOffsetVertical, firstLine, lastLine);

        if (layout instanceof DynamicLayout) {
            if (mTextRenderNodes == null) {
@@ -2154,8 +2160,9 @@ public class Editor {
                    continue;
                }
                startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas, layout,
                        highlight, highlightPaint, cursorOffsetVertical, blockEndLines,
                        blockIndices, i, numberOfBlocks, startIndexToFindAvailableRenderNode);
                        selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
                        blockEndLines, blockIndices, i, numberOfBlocks,
                        startIndexToFindAvailableRenderNode);
                if (blockEndLines[i] >= lastLine) {
                    lastIndex = Math.max(indexFirstChangedBlock, i + 1);
                    break;
@@ -2169,9 +2176,9 @@ public class Editor {
                            || mTextRenderNodes[blockIndex] == null
                            || mTextRenderNodes[blockIndex].needsToBeShifted) {
                        startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas,
                                layout, highlight, highlightPaint, cursorOffsetVertical,
                                blockEndLines, blockIndices, block, numberOfBlocks,
                                startIndexToFindAvailableRenderNode);
                                layout, selectionHighlight, selectionHighlightPaint,
                                cursorOffsetVertical, blockEndLines, blockIndices, block,
                                numberOfBlocks, startIndexToFindAvailableRenderNode);
                    }
                }
            }
+92 −3

File changed.

Preview size limit exceeded, changes collapsed.