ListBuilder.java

/*
 * Copyright 2017 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.slice.builders;

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

import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.util.Consumer;
import androidx.core.util.Pair;
import androidx.slice.Slice;
import androidx.slice.SliceSpecs;
import androidx.slice.builders.impl.ListBuilderBasicImpl;
import androidx.slice.builders.impl.ListBuilderV1Impl;
import androidx.slice.builders.impl.TemplateBuilderImpl;
import androidx.slice.core.SliceHints;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

// TODO: include image examples in the example section when we can (b/111412886)
/**
 * Builder for constructing slices composed of rows of content.
 * <p>
 * A slice is a piece of templated app content that can be presented outside of the app providing
 * the slice in a {@link androidx.slice.widget.SliceView}. To provide a slice you should implement a
 * {@link androidx.slice.SliceProvider} and use ListBuilder to construct your slice when
 * {@link androidx.slice.SliceProvider#onBindSlice(Uri)} is called.
 * </p>
 * <p>
 * ListBuilder allows you to construct a slice made up of rows of content. The row types that
 * ListBuilder supports are:
 * <ul>
 *     <li>{@link HeaderBuilder} - The first row of your slice should be a header. The header
 *     supports a title, subtitle, and tappable row action. A header can also have a summary of the
 *     contents of the slice which can be shown when not all of the slice can be displayed.
 *     </li>
 *     <li>{@link RowBuilder} - A basic row supports title, subtitle, timestamps, and various action
 *     types.
 *     </li>
 *     <li>{@link GridRowBuilder} - A grid row supports cells of vertically laid out content
 *     displayed in a single row.
 *     </li>
 *     <li>{@link RangeBuilder} - A range row supports displaying a horizontal progress indicator.
 *     </li>
 *     <li>{@link InputRangeBuilder} - An input range row supports displaying a horizontal slider
 *     allowing slider input (e.g. brightness or volume slider).
 *     </li>
 * </ul>
 * </p>
 * <b>Handling modes</b>
 * <p>
 * Slices are meant to be presented outside of the app providing them, the slice presenter could be
 * an Android system surface or another application. The presenter will normally use a
 * {@link androidx.slice.widget.SliceView} to display a slice. SliceView supports a couple of
 * different modes. These modes are not controlled by the app providing the slice, but
 * rather by the surface that is presenting the slice. To ensure your slice is presented
 * correctly you should consider the different modes SliceView supports:
 * <ul>
 *     <li>{@link androidx.slice.widget.SliceView#MODE_SMALL} - Only the first row of content is
 *     displayed in small format, normally this will be the header. If no header was set, then the
 *     first row will be used and may appear differently depending on the row type and the
 *     configuration of {@link androidx.slice.widget.SliceView}.
 *     </li>
 *     <li>{@link androidx.slice.widget.SliceView#MODE_LARGE} - As many rows of content are shown
 *     as possible. If the presenter of the slice allows scrolling then all rows of content will
 *     be displayed in a scrollable view.
 *     </li>
 *     <li>{@link androidx.slice.widget.SliceView#MODE_SHORTCUT} - In shortcut mode only a tappable
 *     image is displayed. The image and action used to represent this will be the primary action
 *     of your slice, i.e. {@link HeaderBuilder#setPrimaryAction(SliceAction)}.
 *     </li>
 * </ul>
 * </p>
 * <b>Specifying actions</b>
 * <p>
 * In addition to rows of content, ListBuilder can also have {@link SliceAction}s added to
 * it. These actions may appear differently on your slice depending on how
 * {@link androidx.slice.widget.SliceView} is configured. Normally the actions appear as icons in
 * the header of the slice.
 * </p>
 * <b>How much content to add</b>
 * <p>
 * There is no limit to the number of rows added to ListBuilder, however, the contents of a slice
 * should be related to a primary task, action, or set of information. For example: it might make
 * sense for a slice to manage wi-fi state to have a row for each available network, this might
 * result in a large number of rows but each of these rows serve utility for the primary purpose
 * of the slice which is managing wi-fi.
 * </p>
 * <p>
 * Note that scrolling on SliceView can be disabled, in which case only the header and one or two
 * rows of content may be shown for your slice. If your slice contains many rows of content to
 * scroll through (e.g. list of wifi networks), consider using
 * {@link #setSeeMoreAction(PendingIntent)} to provide a link to open the activity associated with
 * the content.
 * </p>
 *
 * @see HeaderBuilder
 * @see RowBuilder
 * @see GridRowBuilder
 * @see RangeBuilder
 * @see InputRangeBuilder
 * @see SliceAction
 * @see androidx.slice.SliceProvider
 * @see androidx.slice.SliceProvider#onBindSlice(Uri)
 * @see androidx.slice.widget.SliceView
 */
@RequiresApi(19)
public class ListBuilder extends TemplateSliceBuilder {

    private boolean mHasSeeMore;
    private androidx.slice.builders.impl.ListBuilder mImpl;

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
            LARGE_IMAGE, SMALL_IMAGE, ICON_IMAGE, UNKNOWN_IMAGE
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ImageMode{}

    /**
     * Indicates that an image should be presented as an icon and it can be tinted.</p>
     */
    public static final int ICON_IMAGE = SliceHints.ICON_IMAGE;
    /**
     * Indicates that an image should be presented in a smaller size and it shouldn't be tinted.
     */
    public static final int SMALL_IMAGE = SliceHints.SMALL_IMAGE;
    /**
     * Indicates that an image presented in a larger size and it shouldn't be tinted.
     */
    public static final int LARGE_IMAGE = SliceHints.LARGE_IMAGE;
    /**
     * Indicates that an image mode is unknown.
     */
    public static final int UNKNOWN_IMAGE = SliceHints.UNKNOWN_IMAGE;

    /**
     * Constant representing infinity.
     */
    public static final long INFINITY = SliceHints.INFINITY;

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
            View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_INHERIT,
            View.LAYOUT_DIRECTION_LOCALE
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface LayoutDirection{}

    /**
     * Create a builder which will construct a slice made up of rows of content.
     *
     * @param uri Uri to tag for this slice.
     *
     * @hide
     */
    @RestrictTo(LIBRARY)
    public ListBuilder(@NonNull Context context, @NonNull Uri uri) {
        super(context, uri);
    }

    /**
     * Create a ListBuilder for constructing slice content.
     * <p>
     * A slice requires an associated time-to-live, i.e. how long the data contained in the slice
     * can remain fresh. If your slice has content that is not time sensitive, set a TTL of
     * {@link #INFINITY}.
     *
     * @param uri Uri to tag for this slice.
     * @param ttl the length in milliseconds that the content in this slice can live for.
     */
    public ListBuilder(@NonNull Context context, @NonNull Uri uri, long ttl) {
        super(context, uri);
        mImpl.setTtl(ttl);
    }

    /**
     * Create a ListBuilder for constructing slice content.
     * <p>
     * A slice requires an associated time-to-live, i.e. how long the data contained in the slice
     * can remain fresh. If your slice has content that is not time sensitive, set {@link Duration}
     * to null and the TTL will be {@link #INFINITY}.
     *
     * @param uri Uri to tag for this slice.
     * @param ttl the {@link Duration} that the content in this slice can live for.
     */
    @RequiresApi(26)
    public ListBuilder(@NonNull Context context, @NonNull Uri uri, @Nullable Duration ttl) {
        super(context, uri);
        mImpl.setTtl(ttl);
    }

    /**
     * Construct the slice defined by this ListBuilder.
     * <p>
     * Note that a ListBuilder requires a row containing a piece of text that is not created
     * from a {@link GridRowBuilder}. If the first row added does not fulfill this requirement,
     * build the slice will throw {@link IllegalStateException}.
     * <p>
     * Note that a ListBuilder requires a primary action, this can be set on any of the rows added
     * to the list. If a primary action has not been set on any of the rows, building this slice
     * will throw {@link IllegalStateException}.
     *
     * @see HeaderBuilder#setPrimaryAction(SliceAction)
     * @see RowBuilder#setPrimaryAction(SliceAction)
     * @see GridRowBuilder#setPrimaryAction(SliceAction)
     * @see InputRangeBuilder#setPrimaryAction(SliceAction)
     * @see RangeBuilder#setPrimaryAction(SliceAction)
     */
    @NonNull
    @Override
    public Slice build() {
        return ((TemplateBuilderImpl) mImpl).build();
    }

    @Override
    void setImpl(TemplateBuilderImpl impl) {
        mImpl = (androidx.slice.builders.impl.ListBuilder) impl;
    }

    /**
     * Add a row to the list builder.
     */
    @NonNull
    public ListBuilder addRow(@NonNull RowBuilder builder) {
        mImpl.addRow(builder);
        return this;
    }

    /**
     * Add a row to the list builder.
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder addRow(@NonNull Consumer<RowBuilder> c) {
        RowBuilder b = new RowBuilder(this);
        c.accept(b);
        return addRow(b);
    }

    /**
     * Add a grid row to the list builder.
     * <p>
     * Note that grid rows cannot be the first row in your slice. Adding a grid row first without
     * calling {@link #setHeader(HeaderBuilder)} will result in {@link IllegalStateException} when
     * the slice is built.
     */
    @NonNull
    public ListBuilder addGridRow(@NonNull GridRowBuilder builder) {
        mImpl.addGridRow(builder);
        return this;
    }

    /**
     * Add a grid row to the list builder.
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder addGridRow(@NonNull Consumer<GridRowBuilder> c) {
        GridRowBuilder b = new GridRowBuilder(this);
        c.accept(b);
        return addGridRow(b);
    }

    /**
     * Sets a header for this list builder. A list can have only one header. Setting a header allows
     * some flexibility in what's displayed in your slice when SliceView displays in
     * {@link androidx.slice.widget.SliceView#MODE_SMALL} and
     * {@link androidx.slice.widget.SliceView#MODE_SHORTCUT}.
     * <p>
     * In MODE_SMALL, the header row shown if one has been added. The header will also
     * display the {@link HeaderBuilder#setSummary(CharSequence)} text if it has been
     * specified, allowing a summary of otherwise hidden content to be shown.
     * <p>
     * In MODE_SHORTCUT, the primary action set using
     * {@link HeaderBuilder#setPrimaryAction(SliceAction)} will be used for the shortcut
     * representation.
     *
     * @see HeaderBuilder#setSummary(CharSequence)
     */
    @NonNull
    public ListBuilder setHeader(@NonNull HeaderBuilder builder) {
        mImpl.setHeader(builder);
        return this;
    }

    /**
     * Sets a header for this list builder. A list can have only one header. Setting a header allows
     * some flexibility in what's displayed in your slice when SliceView displays in
     * {@link androidx.slice.widget.SliceView#MODE_SMALL} and
     * {@link androidx.slice.widget.SliceView#MODE_SHORTCUT}.
     * <p>
     * In MODE_SMALL, the header row shown if one has been added. The header will also
     * display the {@link HeaderBuilder#setSummary(CharSequence)} text if it has been
     * specified, allowing a summary of otherwise hidden content to be shown.
     * <p>
     * In MODE_SHORTCUT, the primary action set using
     * {@link HeaderBuilder#setPrimaryAction(SliceAction)} will be used for the shortcut
     * representation.
     *
     * @see HeaderBuilder#setSummary(CharSequence)
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder setHeader(@NonNull Consumer<HeaderBuilder> c) {
        HeaderBuilder b = new HeaderBuilder(this);
        c.accept(b);
        return setHeader(b);
    }

    /**
     * Adds an action to this list builder.
     * <p>
     * Actions added with this method are grouped together on the list template and represented
     * as tappable icons or images. These actions may appear differently on the slice depending on
     * how the {@link androidx.slice.widget.SliceView}  is configured. Generally these actions will
     * be  displayed in the order they were added, however, if not all actions can be displayed
     * then actions with a higher priority may be shown first.
     * <p>
     * These actions are only displayed when the slice is displayed in
     * {@link androidx.slice.widget.SliceView#MODE_LARGE} or
     * {@link androidx.slice.widget.SliceView#MODE_SMALL}.
     * <p>
     * These actions differ from a slice's primary action. The primary action
     * is the {@link SliceAction} set on the first row of the slice, normally from
     * {@link HeaderBuilder#setPrimaryAction(SliceAction)}.
     *
     * @see SliceAction
     * @see SliceAction#setPriority(int)
     * @see androidx.slice.widget.SliceView#setSliceActions(List)
     */
    @NonNull
    public ListBuilder addAction(@NonNull SliceAction action) {
        mImpl.addAction(action);
        return this;
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    @NonNull
    public ListBuilder setColor(@ColorInt int color) {
        return setAccentColor(color);
    }

    /**
     * Sets the color to use on tintable items within the list builder.
     * Things that might be tinted are:
     * <ul>
     *     <li>Any {@link SliceAction}s within your slice.
     *     </li>
     *     <li>UI controls such as {@link android.widget.Switch}s or {@link android.widget.SeekBar}s
     *     </li>
     *     <li>Tinting the {@link androidx.slice.widget.SliceView#MODE_SHORTCUT} representation
     *     </li>
     * </ul>
     */
    @NonNull
    public ListBuilder setAccentColor(@ColorInt int color) {
        mImpl.setColor(color);
        return this;
    }

    /**
     * Sets keywords to associate with this slice.
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder setKeywords(List<String> keywords) {
        if (keywords != null) {
            mImpl.setKeywords(new HashSet<>(keywords));
        }
        return this;
    }

    /**
     * Sets keywords to associate with this slice.
     */
    @NonNull
    public ListBuilder setKeywords(Set<String> keywords) {
        mImpl.setKeywords(keywords);
        return this;
    }

    /**
     * Sets the desired layout direction for this slice.
     *
     * @param layoutDirection the layout direction to set.
     */
    @NonNull
    public ListBuilder setLayoutDirection(@LayoutDirection int layoutDirection) {
        mImpl.setLayoutDirection(layoutDirection);
        return this;
    }

    /**
     * If all content in a slice cannot be shown, the row added here may be displayed where the
     * content is cut off. This row should have an affordance to take the user to an activity to
     * see all of the content.
     * <p>
     * This method should only be used if you want to display a customized row to indicate more
     * content, consider using {@link #setSeeMoreAction(PendingIntent)} otherwise. If you do
     * choose to specify a custom row, the row should have a content intent or action end item
     * specified to take the user to an activity to see all of the content. The row added here
     * will only appear when not all content can be displayed and it will not be styled any
     * differently from row built by {@link RowBuilder} normally.
     * </p>
     * <p>
     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
     * a row or action has been previously added.
     * </p>
     */
    @NonNull
    public ListBuilder setSeeMoreRow(@NonNull RowBuilder builder) {
        if (mHasSeeMore) {
            throw new IllegalArgumentException("Trying to add see more row when one has "
                    + "already been added");
        }
        mImpl.setSeeMoreRow(builder);
        mHasSeeMore = true;
        return this;
    }

    /**
     * If all content in a slice cannot be shown, the row added here may be displayed where the
     * content is cut off. This row should have an affordance to take the user to an activity to
     * see all of the content.
     * <p>
     * This method should only be used if you want to display a customized row to indicate more
     * content, consider using {@link #setSeeMoreAction(PendingIntent)} otherwise. If you do
     * choose to specify a custom row, the row should have a content intent or action end item
     * specified to take the user to an activity to see all of the content. The row added here
     * will only appear when not all content can be displayed and it will not be styled any
     * differently from row built by {@link RowBuilder} normally.
     * </p>
     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
     * a row or action has been previously added.
     * </p>
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder setSeeMoreRow(@NonNull Consumer<RowBuilder> c) {
        RowBuilder b = new RowBuilder(this);
        c.accept(b);
        if (mHasSeeMore) {
            throw new IllegalArgumentException("Trying to add see more row when one has "
                    + "already been added");
        }
        mImpl.setSeeMoreRow(b);
        mHasSeeMore = true;
        return this;
    }

    /**
     * If all content in a slice cannot be shown, a "see more" affordance may be displayed where
     * the content is cut off. The action added here should take the user to an activity to see
     * all of the content, and will be invoked when the "see more" affordance is tapped.
     * <p>
     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
     * a row or action has been previously added.
     * </p>
     */
    @NonNull
    public ListBuilder setSeeMoreAction(@NonNull PendingIntent intent) {
        if (mHasSeeMore) {
            throw new IllegalArgumentException("Trying to add see more action when one has "
                    + "already been added");
        }
        mImpl.setSeeMoreAction(intent);
        mHasSeeMore = true;
        return this;
    }

    /**
     * Sets whether this slice indicates an error, i.e. the normal contents of this slice are
     * unavailable and instead the slice contains a message indicating an error.
     */
    @NonNull
    public ListBuilder setIsError(boolean isError) {
        mImpl.setIsError(isError);
        return this;
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    @Override
    protected TemplateBuilderImpl selectImpl(Uri uri) {
        if (checkCompatible(SliceSpecs.LIST, uri)) {
            return new ListBuilderV1Impl(getBuilder(), SliceSpecs.LIST, getClock());
        } else if (checkCompatible(SliceSpecs.BASIC, uri)) {
            return new ListBuilderBasicImpl(getBuilder(), SliceSpecs.BASIC);
        }
        return null;
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    public androidx.slice.builders.impl.ListBuilder getImpl() {
        return mImpl;
    }

    /**
     * Add an input range row to the list builder.
     * <p>
     * If {@link InputRangeBuilder#setValue(int)} is not between
     * {@link InputRangeBuilder#setMin(int)} and {@link InputRangeBuilder#setMax(int)}, this
     * will throw {@link IllegalArgumentException}.
     */
    @NonNull
    public ListBuilder addInputRange(@NonNull InputRangeBuilder b) {
        mImpl.addInputRange(b);
        return this;
    }

    /**
     * Add an input range row to the list builder.
     * <p>
     * If {@link InputRangeBuilder#setValue(int)} is not between
     * {@link InputRangeBuilder#setMin(int)} and {@link InputRangeBuilder#setMax(int)}, this
     * will throw {@link IllegalArgumentException}.
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder addInputRange(@NonNull Consumer<InputRangeBuilder> c) {
        InputRangeBuilder inputRangeBuilder = new InputRangeBuilder(this);
        c.accept(inputRangeBuilder);
        return addInputRange(inputRangeBuilder);
    }

    /**
     * Add a range row to the list builder.
     * <p>
     * If {@link RangeBuilder#setValue(int)} is not between 0 and
     * {@link RangeBuilder#setMax(int)}, this will throw {@link IllegalArgumentException}.
     */
    @NonNull
    public ListBuilder addRange(@NonNull RangeBuilder rangeBuilder) {
        mImpl.addRange(rangeBuilder);
        return this;
    }

    /**
     * Add a range row to the list builder.
     * <p>
     * If {@link RangeBuilder#setValue(int)} is not between 0 and
     * {@link RangeBuilder#setMax(int)}, this will throw {@link IllegalArgumentException}.
     * @hide
     */
    @NonNull
    @RestrictTo(LIBRARY)
    public ListBuilder addRange(@NonNull Consumer<RangeBuilder> c) {
        RangeBuilder rangeBuilder = new RangeBuilder(this);
        c.accept(rangeBuilder);
        return addRange(rangeBuilder);
    }

    /**
     * Builder to construct a range row which can be added to a {@link ListBuilder}.
     * <p>
     * A range row supports displaying a horizontal progress indicator.
     *
     * @see ListBuilder#addRange(RangeBuilder)
     */
    public static class RangeBuilder {

        private int mValue;
        private int mMax = 100;
        private boolean mValueSet = false;
        private CharSequence mTitle;
        private CharSequence mSubtitle;
        private SliceAction mPrimaryAction;
        private CharSequence mContentDescription;
        private int mLayoutDirection = -1;

        /**
         * Builder to construct a range row which can be added to a {@link ListBuilder}.
         * <p>
         * A range row supports displaying a horizontal progress indicator.
         *
         * @see ListBuilder#addRange(RangeBuilder)
         */
        public RangeBuilder() {
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public RangeBuilder(@NonNull ListBuilder parent) {
        }

        /**
         * Set the upper limit of the range. The default is 100.
         */
        @NonNull
        public RangeBuilder setMax(int max) {
            mMax = max;
            return this;
        }

        /**
         * Set the current value of the range.
         *
         * @param value the value of the range, between 0 and {@link #setMax(int)}.
         */
        @NonNull
        public RangeBuilder setValue(int value) {
            mValueSet = true;
            mValue = value;
            return this;
        }

        /**
         * Set the title.
         */
        @NonNull
        public RangeBuilder setTitle(@NonNull CharSequence title) {
            mTitle = title;
            return this;
        }

        /**
         * Set the subtitle.
         */
        @NonNull
        public RangeBuilder setSubtitle(@NonNull CharSequence title) {
            mSubtitle = title;
            return this;
        }

        /**
         * Set the primary action for this row.
         * <p>
         * The action specified here will be sent when the whole row is clicked. If this
         * is the first row in a {@link ListBuilder} this action will also be used to define
         * the {@link androidx.slice.widget.SliceView#MODE_SHORTCUT} representation of the slice.
         */
        @NonNull
        public RangeBuilder setPrimaryAction(@NonNull SliceAction action) {
            mPrimaryAction = action;
            return this;
        }

        /**
         * Sets the content description.
         */
        @NonNull
        public RangeBuilder setContentDescription(@NonNull CharSequence description) {
            mContentDescription = description;
            return this;
        }

        /**
         * Sets the desired layout direction for the content in this row.
         *
         * @param layoutDirection the layout direction to set.
         */
        @NonNull
        public RangeBuilder setLayoutDirection(@LayoutDirection int layoutDirection) {
            mLayoutDirection = layoutDirection;
            return this;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getValue() {
            return mValue;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getMax() {
            return mMax;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isValueSet() {
            return mValueSet;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getTitle() {
            return mTitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getSubtitle() {
            return mSubtitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public SliceAction getPrimaryAction() {
            return mPrimaryAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getContentDescription() {
            return mContentDescription;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getLayoutDirection() {
            return mLayoutDirection;
        }
    }

    /**
     * Builder to construct a input range row.
     * <p>
     * An input range row supports displaying a horizontal slider allowing slider input.
     *
     * @see ListBuilder#addInputRange(InputRangeBuilder)
     */
    public static class InputRangeBuilder {

        private int mMin = 0;
        private int mMax = 100;
        private int mValue = 0;
        private boolean mValueSet = false;
        private CharSequence mTitle;
        private CharSequence mSubtitle;
        private PendingIntent mAction;
        private PendingIntent mInputAction;
        private IconCompat mThumb;
        private SliceAction mPrimaryAction;
        private CharSequence mContentDescription;
        private int mLayoutDirection = -1;

        /**
         * Builder to construct a input range row.
         * <p>
         * An input range row supports displaying a horizontal slider allowing slider input.
         *
         * @see ListBuilder#addInputRange(InputRangeBuilder)
         */
        public InputRangeBuilder() {
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public InputRangeBuilder(@NonNull ListBuilder parent) {
        }

        /**
         * Set the lower limit of the range. The default is 0.
         */
        @NonNull
        public InputRangeBuilder setMin(int min) {
            mMin = min;
            return this;
        }

        /**
         * Set the upper limit of the range. The default is 100.
         */
        @NonNull
        public InputRangeBuilder setMax(int max) {
            mMax = max;
            return this;
        }

        /**
         * Set the current value of the range.
         *
         * @param value the value of the range, between {@link #setMin(int)}
         *              and {@link #setMax(int)}.
         */
        @NonNull
        public InputRangeBuilder setValue(int value) {
            mValueSet = true;
            mValue = value;
            return this;
        }

        /**
         * Set the title.
         */
        @NonNull
        public InputRangeBuilder setTitle(@NonNull CharSequence title) {
            mTitle = title;
            return this;
        }

        /**
         * Set the subtitle.
         */
        @NonNull
        public InputRangeBuilder setSubtitle(@NonNull CharSequence title) {
            mSubtitle = title;
            return this;
        }

        /**
         * Set the {@link PendingIntent} to send when the current value is updated.
         */
        @NonNull
        public InputRangeBuilder setInputAction(@NonNull PendingIntent action) {
            mInputAction = action;
            return this;
        }

        /**
         * Set the {@link Icon} to be displayed as the thumb on the input range.
         */
        @NonNull
        public InputRangeBuilder setThumb(@NonNull IconCompat thumb) {
            mThumb = thumb;
            return this;
        }

        /**
         * Set the primary action for this row.
         * <p>
         * The action specified here will be sent when the whole row is clicked, whereas
         * the action specified via {@link #setInputAction(PendingIntent)} is used when the
         * slider is interacted with. Additionally, if this is the first row in a
         * {@link ListBuilder} this action will also be used to define the
         * {@link androidx.slice.widget.SliceView#MODE_SHORTCUT} representation of the slice.
         */
        @NonNull
        public InputRangeBuilder setPrimaryAction(@NonNull SliceAction action) {
            mPrimaryAction = action;
            return this;
        }

        /**
         * Sets the content description.
         */
        @NonNull
        public InputRangeBuilder setContentDescription(@NonNull CharSequence description) {
            mContentDescription = description;
            return this;
        }

        /**
         * Sets the desired layout direction for the content in this row.
         *
         * @param layoutDirection the layout direction to set.
         */
        @NonNull
        public InputRangeBuilder setLayoutDirection(@LayoutDirection int layoutDirection) {
            mLayoutDirection = layoutDirection;
            return this;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getMin() {
            return mMin;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getMax() {
            return mMax;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getValue() {
            return mValue;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isValueSet() {
            return mValueSet;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getTitle() {
            return mTitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getSubtitle() {
            return mSubtitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public PendingIntent getAction() {
            return mAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public PendingIntent getInputAction() {
            return mInputAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public IconCompat getThumb() {
            return mThumb;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public SliceAction getPrimaryAction() {
            return mPrimaryAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getContentDescription() {
            return mContentDescription;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getLayoutDirection() {
            return mLayoutDirection;
        }
    }

    /**
     * Builder to construct a row. A row can be added as an item to ListBuilder via
     * {@link ListBuilder#addRow(RowBuilder)}.
     * <p>
     * A row supports:
     * <ul>
     *     <li>Title item - The title item can be a timestamp, image, or a {@link SliceAction}.
     *     It appears with at the start of the row. There can only be one title item added to a row.
     *     </li>
     *     <li>Title - Single line of text formatted as a title, see
     *     {@link #setTitle(CharSequence)}.
     *     </li>
     *     <li>Subtitle - Single line of text below the title (if one exists) and is formatted as
     *     normal text, see {@link #setSubtitle(CharSequence)}.
     *     </li>
     *     <li>End item - End items appear at the end of the row. There can be multiple end items
     *     that show depending on available width. End items can be a timestamp, image, or a
     *     tappable icon.
     *     </li>
     *     <li>Primary action - The primary action for the row, this is the action that will be sent
     *     when the row is clicked. This is set via {@link #setPrimaryAction(SliceAction)}. If this
     *     is the only row or first row of the slice, then the action set here will be used to
     *     represent the slice shown in {@link androidx.slice.widget.SliceView#MODE_SMALL}.
     *     </li>
     * </ul>
     * There are a couple of restrictions to how content can be added to a row:
     * <ul>
     *     <li>End items cannot contain a mixture of {@link SliceAction}s and Icons.</li>
     *     <li>There can only be one timestamp added to the row.</li>
     * </ul>
     *
     * @see ListBuilder#addRow(RowBuilder)
     */
    public static class RowBuilder {

        private final Uri mUri;
        private boolean mHasEndActionOrToggle;
        private boolean mHasEndImage;
        private boolean mHasDefaultToggle;
        private boolean mHasTimestamp;
        private long mTimeStamp = -1;
        private boolean mTitleItemLoading;
        private int mTitleImageMode;
        private IconCompat mTitleIcon;
        private SliceAction mTitleAction;
        private SliceAction mPrimaryAction;
        private CharSequence mTitle;
        private boolean mTitleLoading;
        private CharSequence mSubtitle;
        private boolean mSubtitleLoading;
        private CharSequence mContentDescription;
        private int mLayoutDirection = -1;
        private List<Object> mEndItems = new ArrayList<>();
        private List<Integer> mEndTypes = new ArrayList<>();
        private List<Boolean> mEndLoads = new ArrayList<>();
        private boolean mTitleActionLoading;

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public static final int TYPE_TIMESTAMP = 0;
        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public static final int TYPE_ICON = 1;
        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public static final int TYPE_ACTION = 2;

        /**
         * Builder to construct a row.
         */
        public RowBuilder() {
            mUri = null;
        }

        /**
         * Builder to construct a normal row.
         * @param uri Uri to tag for this slice.
         */
        public RowBuilder(Uri uri) {
            mUri = uri;
        }

        /**
         * Builder to construct a row.
         * @param parent The builder constructing the parent slice.
         * @hide
         */
        @RestrictTo(LIBRARY)
        public RowBuilder(@NonNull ListBuilder parent) {
            this();
        }

        /**
         * Builder to construct a row.
         * @param uri Uri to tag for this slice.
         * @hide
         */
        @RestrictTo(LIBRARY)
        public RowBuilder(@NonNull ListBuilder parent, @NonNull Uri uri) {
            this(uri);
        }

        /**
         * Builder to construct a normal row.
         * @param uri Uri to tag for this slice.
         * @hide
         */
        @RestrictTo(LIBRARY)
        public RowBuilder(@NonNull Context context, @NonNull Uri uri) {
            this(uri);
        }

        /**
         * Sets the title item to be the provided timestamp. Only one timestamp can be added, if
         * one is already added this will throw {@link IllegalArgumentException}.
         * <p>
         * There can only be one title item, this will replace any other title
         * items that may have been set.
         */
        @NonNull
        public RowBuilder setTitleItem(long timeStamp) {
            if (mHasTimestamp) {
                throw new IllegalArgumentException("Trying to add a timestamp when one has "
                        + "already been added");
            }
            mTimeStamp = timeStamp;
            mHasTimestamp = true;
            return this;
        }

        /**
         * Sets the title item to be the provided icon. There can only be one title item, this
         * will replace any other title items that may have been set.
         *
         * @param icon the image to display.
         * @param imageMode the mode that image should be displayed in.
         *
         * @see #ICON_IMAGE
         * @see #SMALL_IMAGE
         * @see #LARGE_IMAGE
         */
        public RowBuilder setTitleItem(@NonNull IconCompat icon, @ImageMode int imageMode) {
            return setTitleItem(icon, imageMode, false /* isLoading */);
        }

        /**
         * Sets the title item to be the provided icon. There can only be one title item, this
         * will replace any other title items that may have been set.
         * <p>
         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
         * to load this content in the background, in this case the template displays a placeholder
         * until updated.
         *
         * @param icon the image to display.
         * @param imageMode the mode that image should be displayed in.
         * @param isLoading whether this content is being loaded in the background.
         *
         * @see #ICON_IMAGE
         * @see #SMALL_IMAGE
         * @see #LARGE_IMAGE
         */
        @NonNull
        public RowBuilder setTitleItem(@Nullable IconCompat icon, @ImageMode int imageMode,
                boolean isLoading) {
            mTitleAction = null;
            mTitleIcon = icon;
            mTitleImageMode = imageMode;
            mTitleItemLoading = isLoading;
            return this;
        }

        /**
         * Sets the title item to be a tappable icon. There can only be one title item, this will
         * replace any other title items that may have been set.
         */
        @NonNull
        public RowBuilder setTitleItem(@NonNull SliceAction action) {
            return setTitleItem(action, false /* isLoading */);
        }

        /**
         * Sets the title item to be a tappable icon. There can only be one title item, this will
         * replace any other title items that may have been set.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public RowBuilder setTitleItem(@NonNull SliceAction action, boolean isLoading) {
            mTitleAction = action;
            mTitleIcon = null;
            mTitleImageMode = 0;
            mTitleActionLoading = isLoading;
            return this;
        }

        /**
         * The action specified here will be sent when the whole row is clicked.
         * <p>
         * If this is the first row in a {@link ListBuilder} this action will also be used to define
         * the {@link androidx.slice.widget.SliceView#MODE_SHORTCUT} representation of the slice.
         */
        @NonNull
        public RowBuilder setPrimaryAction(@NonNull SliceAction action) {
            mPrimaryAction = action;
            return this;
        }

        /**
         * Sets the title for the row builder. A title should fit on a single line and is ellipsized
         * if too long.
         */
        @NonNull
        public RowBuilder setTitle(@NonNull CharSequence title) {
            return setTitle(title, false);
        }

        /**
         * Sets the title for the row builder. A title should fit on a single line and is ellipsized
         * if too long.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public RowBuilder setTitle(@Nullable CharSequence title, boolean isLoading) {
            mTitle = title;
            mTitleLoading = isLoading;
            return this;
        }

        /**
         * Sets the subtitle for the row builder. A subtitle should fit on a single line and is
         * ellipsized if too long.
         */
        @NonNull
        public RowBuilder setSubtitle(@NonNull CharSequence subtitle) {
            return setSubtitle(subtitle, false /* isLoading */);
        }

        /**
         * Sets the subtitle for the row builder. A subtitle should fit on a single line and is
         * ellipsized if too long.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public RowBuilder setSubtitle(@Nullable CharSequence subtitle, boolean isLoading) {
            mSubtitle = subtitle;
            mSubtitleLoading = isLoading;
            return this;
        }

        /**
         * Adds a timestamp to the end items of the row builder. Only one timestamp can be added,
         * if one is already added this will throw {@link IllegalArgumentException}.
         */
        @NonNull
        public RowBuilder addEndItem(long timeStamp) {
            if (mHasTimestamp) {
                throw new IllegalArgumentException("Trying to add a timestamp when one has "
                        + "already been added");
            }
            mEndItems.add(timeStamp);
            mEndTypes.add(TYPE_TIMESTAMP);
            mEndLoads.add(false);
            mHasTimestamp = true;
            return this;
        }

        /**
         * Adds an icon to the end items of the row builder.
         *
         * @param icon the image to display.
         * @param imageMode the mode that image should be displayed in.
         *
         * @see #ICON_IMAGE
         * @see #SMALL_IMAGE
         * @see #LARGE_IMAGE
         */
        @NonNull
        public RowBuilder addEndItem(@NonNull IconCompat icon, @ImageMode int imageMode) {
            return addEndItem(icon, imageMode, false /* isLoading */);
        }

        /**
         * Adds an icon to the end items of the row builder.
         * <p>
         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
         * to load this content in the background, in this case the template displays a placeholder
         * until updated.
         *
         * @param icon the image to display.
         * @param imageMode the mode that image should be displayed in.
         * @param isLoading whether this content is being loaded in the background.
         *
         * @see #ICON_IMAGE
         * @see #SMALL_IMAGE
         * @see #LARGE_IMAGE
         */
        @NonNull
        public RowBuilder addEndItem(@Nullable IconCompat icon, @ImageMode int imageMode,
                boolean isLoading) {
            if (mHasEndActionOrToggle) {
                throw new IllegalArgumentException("Trying to add an icon to end items when an"
                        + "action has already been added. End items cannot have a mixture of "
                        + "actions and icons.");
            }
            mEndItems.add(new Pair<>(icon, imageMode));
            mEndTypes.add(TYPE_ICON);
            mEndLoads.add(isLoading);
            mHasEndImage = true;
            return this;
        }

        /**
         * Adds an action to the end items of the row builder. A mixture of icons and
         * actions is not permitted. If an icon has already been added, this will throw
         * {@link IllegalArgumentException}.
         */
        @NonNull
        public RowBuilder addEndItem(@NonNull SliceAction action) {
            return addEndItem(action, false /* isLoading */);
        }

        /**
         * Adds an action to the end items of the row builder. A mixture of icons and
         * actions is not permitted. If an icon has already been added, this will throw
         * {@link IllegalArgumentException}.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public RowBuilder addEndItem(@NonNull SliceAction action, boolean isLoading) {
            if (mHasEndImage) {
                throw new IllegalArgumentException("Trying to add an action to end items when an"
                        + "icon has already been added. End items cannot have a mixture of "
                        + "actions and icons.");
            }
            if (mHasDefaultToggle) {
                throw new IllegalStateException("Only one non-custom toggle can be added "
                        + "in a single row. If you would like to include multiple toggles "
                        + "in a row, set a custom icon for each toggle.");
            }
            mEndItems.add(action);
            mEndTypes.add(TYPE_ACTION);
            mEndLoads.add(isLoading);
            mHasDefaultToggle = action.getImpl().isDefaultToggle();
            mHasEndActionOrToggle = true;
            return this;
        }

        /**
         * Sets the content description for the row.
         */
        @NonNull
        public RowBuilder setContentDescription(@NonNull CharSequence description) {
            mContentDescription = description;
            return this;
        }

        /**
         * Sets the desired layout direction for the content in this row.
         *
         * @param layoutDirection the layout direction to set.
         */
        @NonNull
        public RowBuilder setLayoutDirection(@LayoutDirection int layoutDirection) {
            mLayoutDirection = layoutDirection;
            return this;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public Uri getUri() {
            return mUri;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean hasEndActionOrToggle() {
            return mHasEndActionOrToggle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean hasEndImage() {
            return mHasEndImage;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean hasDefaultToggle() {
            return mHasDefaultToggle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean hasTimestamp() {
            return mHasTimestamp;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public long getTimeStamp() {
            return mTimeStamp;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isTitleItemLoading() {
            return mTitleItemLoading;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getTitleImageMode() {
            return mTitleImageMode;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public IconCompat getTitleIcon() {
            return mTitleIcon;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public SliceAction getTitleAction() {
            return mTitleAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public SliceAction getPrimaryAction() {
            return mPrimaryAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getTitle() {
            return mTitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isTitleLoading() {
            return mTitleLoading;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getSubtitle() {
            return mSubtitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isSubtitleLoading() {
            return mSubtitleLoading;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getContentDescription() {
            return mContentDescription;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getLayoutDirection() {
            return mLayoutDirection;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public List<Object> getEndItems() {
            return mEndItems;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public List<Integer> getEndTypes() {
            return mEndTypes;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public List<Boolean> getEndLoads() {
            return mEndLoads;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isTitleActionLoading() {
            return mTitleActionLoading;
        }
    }

    /**
     * Builder to construct a header row.
     * <p>
     * A header provides some additional functionality compared to a {@link RowBuilder}. Like
     * a row, a header has a title, subtitle, and primary action.
     * <p>
     * In addition to a row's title, subtitle, and primary action, a header also supports setting
     * a summary description of the list contents using
     * {@link HeaderBuilder#setSummary(CharSequence)}. This summary might be used when
     * the rest of the list content is not shown (e.g. if SliceView presenting slice is
     * configured to {@link androidx.slice.widget.SliceView#MODE_SMALL}.
     * <p>
     * The primary action specified by {@link HeaderBuilder#setPrimaryAction(SliceAction)} will
     * be used as the PendingIntent sent when header is clicked. This action is also used when
     * when SliceView displays in {@link androidx.slice.widget.SliceView#MODE_SHORTCUT}.
     * <p>
     * Unlike row builder, header builder does not support end items (e.g.
     * {@link RowBuilder#addEndItem(SliceAction)}). The header may be used to display actions set
     * on the list via {@link #addAction(SliceAction)}.
     *
     * @see ListBuilder#setHeader(HeaderBuilder)
     * @see ListBuilder#addAction(SliceAction)
     * @see SliceAction
     */
    public static class HeaderBuilder {
        private final Uri mUri;
        private CharSequence mTitle;
        private boolean mTitleLoading;
        private CharSequence mSubtitle;
        private boolean mSubtitleLoading;
        private CharSequence mSummary;
        private boolean mSummaryLoading;
        private SliceAction mPrimaryAction;
        private CharSequence mContentDescription;
        private int mLayoutDirection;

        /**
         * Create builder for a header.
         */
        public HeaderBuilder() {
            mUri = null;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP)
        public HeaderBuilder(Uri uri) {
            mUri = uri;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public HeaderBuilder(@NonNull ListBuilder parent) {
            this();
        }

        /**
         * Create builder for a header.
         * @hide
         */
        @RestrictTo(LIBRARY)
        public HeaderBuilder(@NonNull ListBuilder parent, @NonNull Uri uri) {
            this(uri);
        }

        /**
         * Sets the title for the header builder. A title should be representative of the
         * contents of the slice and fit on a single line.
         */
        @NonNull
        public HeaderBuilder setTitle(@NonNull CharSequence title) {
            return setTitle(title, false /* isLoading */);
        }

        /**
         * Sets the title for the header builder. A title should be representative of the
         * contents of the slice and fit on a single line.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public HeaderBuilder setTitle(@NonNull CharSequence title, boolean isLoading) {
            mTitle = title;
            mTitleLoading = isLoading;
            return this;
        }

        /**
         * Sets the subtitle for the header builder. The subtitle should fit on a single line.
         */
        @NonNull
        public HeaderBuilder setSubtitle(@NonNull CharSequence subtitle) {
            return setSubtitle(subtitle, false /* isLoading */);
        }

        /**
         * Sets the subtitle for the header builder. The subtitle should fit on a single line.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public HeaderBuilder setSubtitle(@NonNull CharSequence subtitle, boolean isLoading) {
            mSubtitle = subtitle;
            mSubtitleLoading = isLoading;
            return this;
        }

        /**
         * Sets the summary for the header builder. A summary is optional and should fit on
         * a single line and is ellipsized if too long.
         * <p>
         * The summary should be a description of the contents of the list. This summary might be
         * used when the rest of the list content is not shown (e.g. if SliceView presenting slice
         * is configured to {@link androidx.slice.widget.SliceView#MODE_SMALL}.
         */
        @NonNull
        public HeaderBuilder setSummary(@NonNull CharSequence summary) {
            return setSummary(summary, false /* isLoading */);
        }

        /**
         * Sets the summary for the header builder. A summary is optional and should fit on
         * a single line and is ellipsized if too long.
         * <p>
         * The summary should be a description of the contents of the list. This summary might be
         * used when the rest of the list content is not shown (e.g. if SliceView presenting slice
         * is configured to {@link androidx.slice.widget.SliceView#MODE_SMALL}.
         * <p>
         * Use this method to specify content that will appear in the template once it's been
         * loaded.
         * </p>
         * @param isLoading indicates whether the app is doing work to load the added content in the
         *                  background or not.
         */
        @NonNull
        public HeaderBuilder setSummary(@NonNull CharSequence summary, boolean isLoading) {
            mSummary = summary;
            mSummaryLoading = isLoading;
            return this;
        }

        /**
         * Sets the action to send when the header is clicked.
         * <p>
         * Additionally, the action specified here is used when the slice associated with this
         * header is displayed in {@link androidx.slice.widget.SliceView#MODE_SHORTCUT}.
         */
        @NonNull
        public HeaderBuilder setPrimaryAction(@NonNull SliceAction action) {
            mPrimaryAction = action;
            return this;
        }

        /**
         * Sets the content description for the header.
         */
        @NonNull
        public HeaderBuilder setContentDescription(@NonNull CharSequence description) {
            mContentDescription = description;
            return this;
        }

        /**
         * Sets the desired layout direction for the content in this row.
         *
         * @param layoutDirection the layout direction to set.
         */
        @NonNull
        public HeaderBuilder setLayoutDirection(@LayoutDirection int layoutDirection) {
            mLayoutDirection = layoutDirection;
            return this;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public Uri getUri() {
            return mUri;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getTitle() {
            return mTitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isTitleLoading() {
            return mTitleLoading;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getSubtitle() {
            return mSubtitle;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isSubtitleLoading() {
            return mSubtitleLoading;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getSummary() {
            return mSummary;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public boolean isSummaryLoading() {
            return mSummaryLoading;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public SliceAction getPrimaryAction() {
            return mPrimaryAction;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public CharSequence getContentDescription() {
            return mContentDescription;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        public int getLayoutDirection() {
            return mLayoutDirection;
        }
    }
}