public final class

CarText

extends java.lang.Object

 java.lang.Object

↳androidx.car.app.model.CarText

Gradle dependencies

compile group: 'androidx.car.app', name: 'app', version: '1.7.0-beta01'

  • groupId: androidx.car.app
  • artifactId: app
  • version: 1.7.0-beta01

Artifact androidx.car.app:app:1.7.0-beta01 it located at Google repository (https://maven.google.com/)

Overview

A model that represents text to display in the car screen.

Text handling in the library

Models that consume text strings take a java.lang.CharSequence type as the parameter type. These strings can contain spans that are applied to the text and allow, for example, changing the color of the text, introducing inline images, or displaying a time duration. As described in the span documentation, you can use types such as or to create the strings with the spans

Text spans in strings

The Car App Library only supports a specific set of spans of type CarSpan. Further, individual APIs in the library that take text as input may only support a certain subset of CarSpans. Spans that are not supported will be simply ignored by the host.

By default and unless explicitly documented in the individual APIs that take a text parameter as input, spans for that API are not supported and will be ignored.

For example, the Row.Builder.addText(CharSequence) API documents that ForegroundCarColorSpan instances can be used to color the text of the row. This means any other types of spans except ForegroundCarColorSpan will be ignored.

CarText instances represent the text that was passed by the app through a java.lang.CharSequence, with the non-CarSpan spans removed.

The CarText.toString() method can be used to get a string representation of the string, whereas the CarText.toCharSequence() method returns the reconstructed java.lang.CharSequence, with the nonCarSpan spans removed.

Text variants of multiple lengths

The app is generally agnostic to the width of the views generated by the host that contain the text strings it supplies. For that reason, some models that take text allow the app to pass a list of text variants of different lengths. In those cases the host will pick the variant that best fits the screen. See CarText.Builder.addVariant(CharSequence) for more information.

Text Localization

The host will render the text the app sends without interpretation. The app must localize the text before sending it to the host.

Summary

Methods
public static CarTextcreate(java.lang.CharSequence text)

Returns a CarText instance for the given java.lang.CharSequence.

public booleanequals(java.lang.Object other)

public java.util.List<CarText.SpanWrapper>getSpans()

public java.util.List<java.util.List>getSpansForVariants()

public java.util.List<java.lang.CharSequence>getVariants()

Returns the list of variants for this text.

public inthashCode()

public booleanisEmpty()

Returns whether the text string is empty.

public static booleanisNullOrEmpty(CarText carText)

Returns true if the carText is null or an empty string, false otherwise.

public java.lang.CharSequencetoCharSequence()

Returns the java.lang.CharSequence corresponding to the first text variant.

public static java.lang.StringtoShortString(CarText text)

Returns a shortened string from the input text.

public java.lang.StringtoString()

Returns the string representation of the CarText.

from java.lang.Objectclone, finalize, getClass, notify, notifyAll, wait, wait, wait

Methods

public static boolean isNullOrEmpty(CarText carText)

Returns true if the carText is null or an empty string, false otherwise.

public static CarText create(java.lang.CharSequence text)

Returns a CarText instance for the given java.lang.CharSequence.

Only CarSpan type spans are allowed in a CarText, other spans will be removed from the provided java.lang.CharSequence.

public boolean isEmpty()

Returns whether the text string is empty.

Only the first variant is checked.

public java.lang.String toString()

Returns the string representation of the CarText.

Only the first variant is returned.

public java.lang.CharSequence toCharSequence()

Returns the java.lang.CharSequence corresponding to the first text variant.

Spans that are not of type CarSpan that were passed when creating the CarText instance will not be present in the returned java.lang.CharSequence.

See also: CarText.create(CharSequence)

public java.util.List<java.lang.CharSequence> getVariants()

Returns the list of variants for this text.

Only the variants set with CarText.Builder.addVariant(CharSequence) will be returned. To get the first variant, use CarText.toCharSequence().

Spans that are not of type CarSpan that were passed when creating the CarText instance will not be present in the returned java.lang.CharSequence.

See also: CarText.Builder.addVariant(CharSequence)

public java.util.List<CarText.SpanWrapper> getSpans()

public java.util.List<java.util.List> getSpansForVariants()

public static java.lang.String toShortString(CarText text)

Returns a shortened string from the input text.

public boolean equals(java.lang.Object other)

public int hashCode()

Source

/*
 * Copyright 2020 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 androidx.car.app.model;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import static java.util.Objects.requireNonNull;

import android.text.SpannableString;
import android.text.Spanned;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.car.app.annotations.CarProtocol;
import androidx.car.app.annotations.KeepFields;
import androidx.car.app.annotations.RequiresCarApi;
import androidx.car.app.utils.CollectionUtils;
import androidx.car.app.utils.StringUtils;

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

/**
 * A model that represents text to display in the car screen.
 *
 * <h2>Text handling in the library</h2>
 *
 * Models that consume text strings take a {@link CharSequence} type as the parameter type. These
 * strings can contain spans that are applied to the text and allow, for example, changing the
 * color of the text, introducing inline images, or displaying a time duration.
 * As described in
 * <a href="https://developer.android.com/guide/topics/text/spans">the span documentation</a>,
 * you can use types such as {@link SpannableString} or {@link android.text.SpannedString} to
 * create the strings with the spans
 *
 * <h4>Text spans in strings</h4>
 *
 * <p>The Car App Library only supports a specific set of spans of type {@link CarSpan}. Further,
 * individual APIs in the library that take text as input may only support a certain subset of
 * {@link CarSpan}s. Spans that are not supported will be simply ignored by the host.
 *
 * <p>By default and unless explicitly documented in the individual APIs that take a text
 * parameter as input, spans for that API are not supported and will be ignored.
 *
 * <p>For example, the {@link Row.Builder#addText(CharSequence)} API documents that
 * {@link ForegroundCarColorSpan} instances can be used to color the text of the row. This means any
 * other types of spans except {@link ForegroundCarColorSpan} will be ignored.
 *
 * <p>{@link CarText} instances represent the text that was passed by the app through a
 * {@link CharSequence}, with the non-{@link CarSpan} spans removed.
 *
 * <p>The {@link CarText#toString} method can be used to get a string representation of the string,
 * whereas the {@link CarText#toCharSequence()} method returns the reconstructed
 * {@link CharSequence}, with the non{@link CarSpan} spans removed.
 *
 * <h4>Text variants of multiple lengths</h4>
 *
 * <p>The app is generally agnostic to the width of the views generated by the host that contain
 * the text strings it supplies. For that reason, some models that take text allow the app to
 * pass a list of text variants of different lengths. In those cases the host will pick the
 * variant that best fits the screen. See {@link Builder#addVariant} for more information.
 *
 * <h4>Text Localization</h4>
 *
 * <p>The host will render the text the app sends without interpretation. The app must localize
 * the text before sending it to the host.
 */
@CarProtocol
@KeepFields
public final class CarText {
    private final String mText;
    private final List<String> mTextVariants;
    private final List<SpanWrapper> mSpans;
    private final List<List<SpanWrapper>> mSpansForVariants;

    /**
     * Returns {@code true} if the {@code carText} is {@code null} or an empty string, {@code
     * false} otherwise.
     */
    public static boolean isNullOrEmpty(@Nullable CarText carText) {
        return carText == null || carText.isEmpty();
    }

    /**
     * Returns a {@link CarText} instance for the given {@link CharSequence}.
     *
     * <p>Only {@link CarSpan} type spans are allowed in a {@link CarText}, other spans will be
     * removed from the provided {@link CharSequence}.
     *
     * @throws NullPointerException if the text is {@code null}
     */
    @NonNull
    public static CarText create(@NonNull CharSequence text) {
        return new CarText(requireNonNull(text));
    }

    /**
     * Returns whether the text string is empty.
     *
     * <p>Only the first variant is checked.
     */
    public boolean isEmpty() {
        return mText.isEmpty();
    }

    /**
     * Returns the string representation of the {@link CarText}.
     *
     * <p>Only the first variant is returned.
     */
    @NonNull
    @Override
    public String toString() {
        return mText;
    }

    /**
     * Returns the {@link CharSequence} corresponding to the first text variant.
     *
     * <p>Spans that are not of type {@link CarSpan} that were passed when creating the
     * {@link CarText} instance will not be present in the returned {@link CharSequence}.
     *
     * @see CarText#create(CharSequence)
     */
    @NonNull
    public CharSequence toCharSequence() {
        return getCharSequence(mText, mSpans);
    }

    /**
     * Returns the list of variants for this text.
     *
     * <p>Only the variants set with {@link Builder#addVariant(CharSequence)} will be returned.
     * To get the first variant, use {@link CarText#toCharSequence}.
     *
     * <p>Spans that are not of type {@link CarSpan} that were passed when creating the
     * {@link CarText} instance will not be present in the returned {@link CharSequence}.
     *
     * @see Builder#addVariant(CharSequence)
     */
    @NonNull
    public List<CharSequence> getVariants() {
        if (mTextVariants.isEmpty()) {
            return Collections.emptyList();
        }

        List<CharSequence> charSequences = new ArrayList<>();
        for (int i = 0; i < mTextVariants.size(); i++) {
            charSequences.add(getCharSequence(mTextVariants.get(i), mSpansForVariants.get(i)));
        }
        return Collections.unmodifiableList(charSequences);
    }

    /**
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public List<SpanWrapper> getSpans() {
        return mSpans;
    }

    /**
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public List<List<SpanWrapper>> getSpansForVariants() {
        return mSpansForVariants;
    }

    /**
     * Returns a shortened string from the input {@code text}.
     *
     */
    @RestrictTo(LIBRARY)
    @Nullable
    public static String toShortString(@Nullable CarText text) {
        return text == null ? null : StringUtils.shortenString(text.toString());
    }

    private CarText() {
        mText = "";
        mSpans = Collections.emptyList();
        mTextVariants = Collections.emptyList();
        mSpansForVariants = Collections.emptyList();
    }

    CarText(CharSequence text) {
        mText = text.toString();
        mSpans = getSpans(text);
        mTextVariants = Collections.emptyList();
        mSpansForVariants = Collections.emptyList();
    }

    CarText(Builder builder) {
        mText = builder.mText.toString();
        mSpans = getSpans(builder.mText);

        List<CharSequence> textVariants = builder.mTextVariants;
        List<String> textList = new ArrayList<>();
        List<List<SpanWrapper>> spanList = new ArrayList<>();
        for (int i = 0; i < textVariants.size(); i++) {
            CharSequence text = textVariants.get(i);
            textList.add(text.toString());
            spanList.add(getSpans(text));
        }
        mTextVariants = CollectionUtils.unmodifiableCopy(textList);
        mSpansForVariants = CollectionUtils.unmodifiableCopy(spanList);
    }

    private static List<SpanWrapper> getSpans(CharSequence text) {
        List<SpanWrapper> spans = new ArrayList<>();
        if (text instanceof Spanned) {
            Spanned spanned = (Spanned) text;

            for (Object span : spanned.getSpans(0, text.length(), Object.class)) {
                if (span instanceof CarSpan) {
                    spans.add(new SpanWrapper(spanned, (CarSpan) span));
                }
            }
        }
        return CollectionUtils.unmodifiableCopy(spans);
    }

    private static CharSequence getCharSequence(String text, List<SpanWrapper> spans) {
        SpannableString spannableString = new SpannableString(text);
        for (SpanWrapper spanWrapper : CollectionUtils.emptyIfNull(spans)) {
            spannableString.setSpan(
                    spanWrapper.getCarSpan(),
                    spanWrapper.getStart(),
                    spanWrapper.getEnd(),
                    spanWrapper.getFlags());
        }
        return spannableString;
    }

    @Override
    public boolean equals(@Nullable Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof CarText)) {
            return false;
        }
        CarText otherText = (CarText) other;
        return Objects.equals(mText, otherText.mText)
                && Objects.equals(mSpans, otherText.mSpans)
                && Objects.equals(mTextVariants, otherText.mTextVariants)
                && Objects.equals(mSpansForVariants, otherText.mSpansForVariants);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mText, mSpans, mTextVariants, mSpansForVariants);
    }

    /**
     * Wraps a span to send it to the host.
     *
     */
    @RestrictTo(LIBRARY)
    @KeepFields
    public static class SpanWrapper {
        private final int mStart;
        private final int mEnd;
        private final int mFlags;
        @NonNull
        private final CarSpan mCarSpan;

        SpanWrapper(@NonNull Spanned spanned, @NonNull CarSpan carSpan) {
            mStart = spanned.getSpanStart(carSpan);
            mEnd = spanned.getSpanEnd(carSpan);
            mFlags = spanned.getSpanFlags(carSpan);
            mCarSpan = carSpan;
        }

        SpanWrapper() {
            mStart = 0;
            mEnd = 0;
            mFlags = 0;
            mCarSpan = new CarSpan();
        }

        public int getStart() {
            return mStart;
        }

        public int getEnd() {
            return mEnd;
        }

        public int getFlags() {
            return mFlags;
        }

        @NonNull
        public CarSpan getCarSpan() {
            return mCarSpan;
        }

        @Override
        public boolean equals(@Nullable Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof SpanWrapper)) {
                return false;
            }
            SpanWrapper wrapper = (SpanWrapper) other;
            return mStart == wrapper.mStart
                    && mEnd == wrapper.mEnd
                    && mFlags == wrapper.mFlags
                    && Objects.equals(mCarSpan, wrapper.mCarSpan);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mStart, mEnd, mFlags, mCarSpan);
        }

        @NonNull
        @Override
        public String toString() {
            return "[" + mCarSpan + ": " + mStart + ", " + mEnd + ", flags: " + mFlags + "]";
        }
    }

    /** A builder of {@link CarText}. */
    @KeepFields
    public static final class Builder {
        CharSequence mText;
        List<CharSequence> mTextVariants = new ArrayList<>();

        /**
         * Returns a new instance of a {@link Builder}.
         *
         * <p>Only {@link CarSpan} type spans are allowed in a {@link CarText}, other spans will be
         * removed from the provided {@link CharSequence}.
         *
         * @param text the first variant of the text to use. This represents the app's preferred
         *             text variant. Other alternatives can be supplied with
         *             {@link Builder#addVariant}.
         * @throws NullPointerException if the text is {@code null}
         * @see Builder#addVariant(CharSequence)
         */
        public Builder(@NonNull CharSequence text) {
            mText = requireNonNull(text);
        }

        /**
         * Adds a text variant for the {@link CarText} instance.
         *
         * <p>Only {@link CarSpan} type spans are allowed in a {@link CarText}, other spans will be
         * removed from the provided {@link CharSequence}.
         *
         * <p>The text variants should be added in order of preference, from most to least
         * preferred (for instance, from longest to shortest). If the text provided via
         * {@link #Builder} does not fit in the screen, the host will display the
         * first variant that fits in the screen.
         *
         * <p>For instance, if the variant order is ["long string", "shorter", "short"], and the
         * screen can fit 7 characters, "shorter" will be chosen. However, if the order is
         * ["short", "shorter", "long string"], "short" will be chosen, because "short" fits
         * within the 7 character limit.
         *
         * @throws NullPointerException if the text is {@code null}
         */
        @RequiresCarApi(2)
        @NonNull
        public Builder addVariant(@NonNull CharSequence text) {
            mTextVariants.add(requireNonNull(text));
            return this;
        }

        /**
         * Constructs the {@link CarText} defined by this builder.
         */
        @NonNull
        public CarText build() {
            return new CarText(this);
        }
    }
}