public class

ListBuilderImpl

extends TemplateBuilderImpl

implements ListBuilder

 java.lang.Object

androidx.slice.builders.impl.TemplateBuilderImpl

↳androidx.slice.builders.impl.ListBuilderImpl

Gradle dependencies

compile group: 'androidx.slice', name: 'slice-builders', version: '1.1.0-alpha02'

  • groupId: androidx.slice
  • artifactId: slice-builders
  • version: 1.1.0-alpha02

Artifact androidx.slice:slice-builders:1.1.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.slice:slice-builders com.android.support:slices-builders

Summary

Constructors
publicListBuilderImpl(Slice.Builder b, SliceSpec spec)

publicListBuilderImpl(Slice.Builder b, SliceSpec spec, Clock clock)

Methods
public voidaddAction(SliceAction action)

public voidaddGridRow(GridRowBuilder builder)

public voidaddInputRange(ListBuilder.InputRangeBuilder builder)

public voidaddRange(ListBuilder.RangeBuilder builder)

public voidaddRating(ListBuilder.RatingBuilder builder)

public voidaddRow(ListBuilder.RowBuilder builder)

Add a row to list builder.

public voidaddRow(ListBuilderImpl.RowBuilderImpl builder)

Add a row to list builder.

public voidaddSelection(SelectionBuilder builder)

public abstract voidapply(Slice.Builder builder)

public Slicebuild()

Construct the slice.

public voidsetColor(int color)

public voidsetHeader(ListBuilder.HeaderBuilder builder)

public voidsetHostExtras(PersistableBundle extras)

public voidsetIsError(boolean isError)

public voidsetKeywords(java.util.Set<java.lang.String> keywords)

public voidsetLayoutDirection(int layoutDirection)

public voidsetSeeMoreAction(PendingIntent intent)

public voidsetSeeMoreRow(ListBuilder.RowBuilder builder)

public voidsetTtl(java.time.Duration ttl)

public voidsetTtl(long ttl)

from TemplateBuilderImplcreateChildBuilder, getBuilder, getClock, getSpec, parseImageMode, setBuilder
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ListBuilderImpl(Slice.Builder b, SliceSpec spec)

public ListBuilderImpl(Slice.Builder b, SliceSpec spec, Clock clock)

Methods

public abstract void apply(Slice.Builder builder)

public Slice build()

Construct the slice.

public void addRow(ListBuilder.RowBuilder builder)

Add a row to list builder.

public void addRow(ListBuilderImpl.RowBuilderImpl builder)

Add a row to list builder.

public void addGridRow(GridRowBuilder builder)

public void setHeader(ListBuilder.HeaderBuilder builder)

public void addAction(SliceAction action)

public void addRating(ListBuilder.RatingBuilder builder)

public void addInputRange(ListBuilder.InputRangeBuilder builder)

public void addRange(ListBuilder.RangeBuilder builder)

public void addSelection(SelectionBuilder builder)

public void setSeeMoreRow(ListBuilder.RowBuilder builder)

public void setSeeMoreAction(PendingIntent intent)

public void setColor(int color)

public void setKeywords(java.util.Set<java.lang.String> keywords)

public void setTtl(long ttl)

public void setTtl(java.time.Duration ttl)

public void setIsError(boolean isError)

public void setLayoutDirection(int layoutDirection)

public void setHostExtras(PersistableBundle extras)

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.slice.builders.impl;

import static android.app.slice.Slice.HINT_ACTIONS;
import static android.app.slice.Slice.HINT_ERROR;
import static android.app.slice.Slice.HINT_KEYWORDS;
import static android.app.slice.Slice.HINT_LAST_UPDATED;
import static android.app.slice.Slice.HINT_LIST_ITEM;
import static android.app.slice.Slice.HINT_PARTIAL;
import static android.app.slice.Slice.HINT_SEE_MORE;
import static android.app.slice.Slice.HINT_SHORTCUT;
import static android.app.slice.Slice.HINT_SUMMARY;
import static android.app.slice.Slice.HINT_TITLE;
import static android.app.slice.Slice.HINT_TTL;
import static android.app.slice.Slice.SUBTYPE_COLOR;
import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION;
import static android.app.slice.Slice.SUBTYPE_LAYOUT_DIRECTION;
import static android.app.slice.Slice.SUBTYPE_MAX;
import static android.app.slice.Slice.SUBTYPE_RANGE;
import static android.app.slice.Slice.SUBTYPE_VALUE;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_BUNDLE;
import static android.app.slice.SliceItem.FORMAT_SLICE;
import static android.app.slice.SliceItem.FORMAT_TEXT;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.slice.Slice.SUBTYPE_RANGE_MODE;
import static androidx.slice.builders.ListBuilder.INFINITY;
import static androidx.slice.builders.ListBuilder.RANGE_MODE_DETERMINATE;
import static androidx.slice.builders.ListBuilder.RANGE_MODE_STAR_RATING;
import static androidx.slice.core.SliceHints.HINT_END_OF_SECTION;
import static androidx.slice.core.SliceHints.SUBTYPE_HOST_EXTRAS;
import static androidx.slice.core.SliceHints.SUBTYPE_MILLIS;
import static androidx.slice.core.SliceHints.SUBTYPE_MIN;
import static androidx.slice.core.SliceHints.SUBTYPE_SELECTION;

import android.app.PendingIntent;
import android.net.Uri;
import android.os.Bundle;
import android.os.PersistableBundle;

import androidx.annotation.ColorInt;
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.Pair;
import androidx.slice.Clock;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.SliceSpec;
import androidx.slice.SliceSpecs;
import androidx.slice.SystemClock;
import androidx.slice.builders.GridRowBuilder;
import androidx.slice.builders.ListBuilder.HeaderBuilder;
import androidx.slice.builders.ListBuilder.InputRangeBuilder;
import androidx.slice.builders.ListBuilder.RangeBuilder;
import androidx.slice.builders.ListBuilder.RatingBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
import androidx.slice.builders.SelectionBuilder;
import androidx.slice.builders.SliceAction;
import androidx.slice.core.SliceQuery;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @hide
 */
@RestrictTo(LIBRARY)
@RequiresApi(19)
public class ListBuilderImpl extends TemplateBuilderImpl implements ListBuilder {
    private List<Slice> mSliceActions;
    private Set<String> mKeywords;
    private Slice mSliceHeader;
    private boolean mIsError;
    private boolean mFirstRowChecked;
    private boolean mIsFirstRowTypeValid;
    private boolean mFirstRowHasText;
    private Bundle mHostExtras;

    public ListBuilderImpl(@Nullable final Slice.Builder b, @Nullable final SliceSpec spec) {
        this(b, spec, new SystemClock());
    }

    /**
     */
    public ListBuilderImpl(@Nullable final Slice.Builder b, @Nullable final SliceSpec spec,
            @NonNull final Clock clock) {
        super(b, spec, clock);
    }

    /**
     */
    @Override
    public void apply(@NonNull final Slice.Builder builder) {
        builder.addLong(getClock().currentTimeMillis(), SUBTYPE_MILLIS, HINT_LAST_UPDATED);
        if (mSliceHeader != null) {
            builder.addSubSlice(mSliceHeader);
        }
        if (mSliceActions != null) {
            Slice.Builder sb = new Slice.Builder(builder);
            for (int i = 0; i < mSliceActions.size(); i++) {
                sb.addSubSlice(mSliceActions.get(i));
            }
            builder.addSubSlice(sb.addHints(HINT_ACTIONS).build());
        }
        if (mIsError) {
            builder.addHints(HINT_ERROR);
        }
        if (mKeywords != null) {
            Slice.Builder sb = new Slice.Builder(getBuilder());
            for (String keyword : mKeywords) {
                sb.addText(keyword, null);
            }
            getBuilder().addSubSlice(sb.addHints(HINT_KEYWORDS).build());
        }
        if (mHostExtras != null) {
            builder.addItem(new SliceItem(mHostExtras, FORMAT_BUNDLE, SUBTYPE_HOST_EXTRAS,
                    new String[0]));
        }
    }

    /**
     * Construct the slice.
     */
    @Override
    @NonNull
    public Slice build() {
        Slice slice = super.build();
        boolean isLoading = SliceQuery.find(slice, null, HINT_PARTIAL, null) != null;
        boolean isEmpty = SliceQuery.find(slice, FORMAT_SLICE, HINT_LIST_ITEM, null) == null;
        String[] hints = new String[] {HINT_SHORTCUT, HINT_TITLE};
        SliceItem action = SliceQuery.find(slice, FORMAT_ACTION, hints, null);
        List<SliceItem> possiblePrimaries = SliceQuery.findAll(slice, FORMAT_SLICE, hints, null);
        if (!isLoading && !isEmpty && action == null
                && (possiblePrimaries == null || possiblePrimaries.isEmpty())) {
            throw new IllegalStateException("A slice requires a primary action; ensure one of your "
                    + "builders has called #setPrimaryAction with a valid SliceAction.");
        }
        if (mFirstRowChecked && !mIsFirstRowTypeValid) {
            throw new IllegalStateException("A slice cannot have the first row be"
                    + " constructed from a GridRowBuilder, consider using #setHeader.");
        }
        if (mFirstRowChecked && !mFirstRowHasText) {
            throw new IllegalStateException("A slice requires the first row to have some text.");
        }
        return slice;
    }

    /**
     * Add a row to list builder.
     */
    @Override
    public void addRow(@NonNull RowBuilder builder) {
        RowBuilderImpl impl = new RowBuilderImpl(createChildBuilder());
        impl.fillFrom(builder);
        checkRow(true, impl.hasText());
        addRow(impl);
    }

    /**
     * Add a row to list builder.
     */
    public void addRow(@NonNull RowBuilderImpl builder) {
        checkRow(true, builder.hasText());
        builder.getBuilder().addHints(HINT_LIST_ITEM);
        if (builder.isEndOfSection()) {
            builder.getBuilder().addHints(HINT_END_OF_SECTION);
        }
        getBuilder().addSubSlice(builder.build());
    }

    /**
     */
    @Override
    public void addGridRow(@NonNull GridRowBuilder builder) {
        checkRow(false, false);
        GridRowBuilderListV1Impl impl = new GridRowBuilderListV1Impl(this, builder);
        impl.getBuilder().addHints(HINT_LIST_ITEM);
        getBuilder().addSubSlice(impl.build());
    }

    /**
     */
    @Override
    public void setHeader(@NonNull HeaderBuilder builder) {
        mIsFirstRowTypeValid = true;
        mFirstRowHasText = true;
        mFirstRowChecked = true;
        HeaderBuilderImpl impl = new HeaderBuilderImpl(this);
        impl.fillFrom(builder);
        mSliceHeader = impl.build();
    }

    /**
     */
    @Override
    public void addAction(@NonNull SliceAction action) {
        if (mSliceActions == null) {
            mSliceActions = new ArrayList<>();
        }
        Slice.Builder b = new Slice.Builder(getBuilder()).addHints(HINT_ACTIONS);
        mSliceActions.add(action.buildSlice(b));
    }

    @Override
    public void addRating(@NonNull final RatingBuilder builder) {
        RatingBuilderImpl impl = new RatingBuilderImpl(createChildBuilder(), builder);
        checkRow(true, impl.hasText());
        getBuilder().addSubSlice(impl.build(), SUBTYPE_RANGE);
    }

    @Override
    public void addInputRange(@NonNull final InputRangeBuilder builder) {
        InputRangeBuilderImpl impl = new InputRangeBuilderImpl(createChildBuilder(), builder);
        checkRow(true, impl.hasText());
        getBuilder().addSubSlice(impl.build(), SUBTYPE_RANGE);
    }

    @Override
    public void addRange(@NonNull final RangeBuilder builder) {
        RangeBuilderImpl impl = new RangeBuilderImpl(createChildBuilder(), builder);
        checkRow(true, impl.hasText());
        getBuilder().addSubSlice(impl.build(), SUBTYPE_RANGE);
    }

    @Override
    public void addSelection(@NonNull final SelectionBuilder builder) {
        if (getSpec().canRender(SliceSpecs.LIST_V2)) {
            getBuilder().addSubSlice(
                    new SelectionBuilderListV2Impl(createChildBuilder(), builder).build(),
                    SUBTYPE_SELECTION);
        } else {
            getBuilder().addSubSlice(
                    new SelectionBuilderBasicImpl(createChildBuilder(), builder).build());
        }
    }

    /**
     */
    @Override
    public void setSeeMoreRow(@NonNull final RowBuilder builder) {
        RowBuilderImpl impl = new RowBuilderImpl(createChildBuilder());
        impl.fillFrom(builder);
        impl.getBuilder().addHints(HINT_SEE_MORE);
        getBuilder().addSubSlice(impl.build());
    }

    /**
     */
    @Override
    public void setSeeMoreAction(@NonNull final PendingIntent intent) {
        getBuilder().addSubSlice(
                new Slice.Builder(getBuilder())
                        .addHints(HINT_SEE_MORE)
                        .addAction(intent, new Slice.Builder(getBuilder())
                                .addHints(HINT_SEE_MORE).build(), null)
                        .build());
    }

    /**
     */
    @Override
    public void setColor(@ColorInt int color) {
        getBuilder().addInt(color, SUBTYPE_COLOR);
    }

    /**
     */
    @Override
    public void setKeywords(@NonNull Set<String> keywords) {
        mKeywords = keywords;
    }

    /**
     */
    @Override
    public void setTtl(long ttl) {
        long expiry = ttl == INFINITY ? INFINITY : getClock().currentTimeMillis() + ttl;
        getBuilder().addTimestamp(expiry, SUBTYPE_MILLIS, HINT_TTL);
    }

    @Override
    @RequiresApi(26)
    public void setTtl(@Nullable Duration ttl) {
        setTtl(ttl == null ? INFINITY : ttl.toMillis());
    }

    @Override
    public void setIsError(boolean isError) {
        mIsError = isError;
    }

    @Override
    public void setLayoutDirection(int layoutDirection) {
        getBuilder().addInt(layoutDirection, SUBTYPE_LAYOUT_DIRECTION);
    }

    @Override
    @RequiresApi(21)
    public void setHostExtras(@NonNull PersistableBundle extras) {
        mHostExtras = ConvertPersistableBundleApi21Impl.toBundle(extras);
    }

    @RequiresApi(21)
    private static class ConvertPersistableBundleApi21Impl {
        private ConvertPersistableBundleApi21Impl() {}
        static Bundle toBundle(@NonNull PersistableBundle extras) {
            return new Bundle(extras);
        }
    }


    /**
     * There are some requirements that first row of a list is not a grid row and has some text.
     * This method helps check whether first row fulfils these requirements.
     */
    private void checkRow(boolean isTypeValid, boolean hasText) {
        if (!mFirstRowChecked) {
            mFirstRowChecked = true;
            mIsFirstRowTypeValid = isTypeValid;
            mFirstRowHasText = hasText;
        }
    }

    /**
     * Builder to construct an input row.
     */
    public static class RangeBuilderImpl extends TemplateBuilderImpl {
        protected int mMin = 0;
        protected int mMax = 100;
        protected int mValue = 0;
        protected boolean mValueSet = false;
        @Nullable
        protected CharSequence mTitle;
        @Nullable
        protected CharSequence mSubtitle;
        @Nullable
        protected CharSequence mContentDescr;
        @Nullable
        protected SliceAction mPrimaryAction;
        protected int mLayoutDir = -1;
        private int mMode = RANGE_MODE_DETERMINATE;
        private Slice mStartItem;

        RangeBuilderImpl(Slice.Builder sb, RangeBuilder builder) {
            super(sb, null);
            if (builder != null) {
                mValueSet = builder.isValueSet();
                mMax = builder.getMax();
                mValue = builder.getValue();
                mTitle = builder.getTitle();
                mSubtitle = builder.getSubtitle();
                mContentDescr = builder.getContentDescription();
                mPrimaryAction = builder.getPrimaryAction();
                mLayoutDir = builder.getLayoutDirection();
                mMode = builder.getMode();
                if (builder.getTitleIcon() != null) {
                    setTitleItem(builder.getTitleIcon(), builder.getTitleImageMode(),
                            builder.isTitleItemLoading());
                }
            }
        }

        void setTitleItem(IconCompat icon, int imageMode, boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder())
                    .addIcon(icon, null, parseImageMode(imageMode, isLoading));
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mStartItem = sb.addHints(HINT_TITLE).build();
        }

        @Override
        public void apply(@NonNull Slice.Builder builder) {
            if (!mValueSet) {
                // Unset, make it whatever min is
                mValue = mMin;
            }
            if (!(mMin <= mValue && mValue <= mMax && mMin < mMax))  {
                throw new IllegalArgumentException(
                        "Invalid range values, min=" + mMin + ", value=" + mValue + ", max=" + mMax
                + " ensure value falls within (min, max) and min < max.");
            }

            if (mStartItem != null) {
                builder.addSubSlice(mStartItem);
            }
            if (mTitle != null) {
                builder.addText(mTitle, null, HINT_TITLE);
            }
            if (mSubtitle != null) {
                builder.addText(mSubtitle, null);
            }
            if (mContentDescr != null) {
                builder.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION);
            }
            if (mPrimaryAction != null) {
                mPrimaryAction.setPrimaryAction(builder);
            }
            if (mLayoutDir != -1) {
                builder.addInt(mLayoutDir, SUBTYPE_LAYOUT_DIRECTION);
            }
            builder.addHints(HINT_LIST_ITEM)
                    .addInt(mMin, SUBTYPE_MIN)
                    .addInt(mMax, SUBTYPE_MAX)
                    .addInt(mValue, SUBTYPE_VALUE)
                    .addInt(mMode, SUBTYPE_RANGE_MODE);
        }
        boolean hasText() {
            return mTitle != null || mSubtitle != null;
        }
    }

    /**
     * Builder to construct an input range row.
     */
    public static class InputRangeBuilderImpl extends RangeBuilderImpl {
        private final PendingIntent mAction;
        private final IconCompat mThumb;
        private Slice mStartItem;
        private final ArrayList<Slice> mEndItems = new ArrayList<>();

        InputRangeBuilderImpl(Slice.Builder sb, InputRangeBuilder builder) {
            super(sb, null);
            mValueSet = builder.isValueSet();
            mMin = builder.getMin();
            mMax = builder.getMax();
            mValue = builder.getValue();
            mTitle = builder.getTitle();
            mSubtitle = builder.getSubtitle();
            mContentDescr = builder.getContentDescription();
            mPrimaryAction = builder.getPrimaryAction();
            mLayoutDir = builder.getLayoutDirection();
            mAction = builder.getInputAction();
            mThumb = builder.getThumb();
            if (builder.getTitleIcon() != null) {
                setTitleItem(builder.getTitleIcon(), builder.getTitleImageMode(),
                        builder.isTitleItemLoading());
            }
            List<Object> endItems = builder.getEndItems();
            List<Integer> endTypes = builder.getEndTypes();
            List<Boolean> endLoads = builder.getEndLoads();
            for (int i = 0; i < endItems.size(); i++) {
                if (endTypes.get(i) == InputRangeBuilder.TYPE_ACTION) {
                    addEndItem((SliceAction) endItems.get(i), endLoads.get(i));
                }
            }
        }

        @Override
        void setTitleItem(IconCompat icon, int imageMode, boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder())
                    .addIcon(icon, null, parseImageMode(imageMode, isLoading));
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mStartItem = sb.addHints(HINT_TITLE).build();
        }

        private void addEndItem(@NonNull SliceAction action, boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder());
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mEndItems.add(action.buildSlice(sb));
        }

        @Override
        public void apply(@NonNull Slice.Builder builder) {
            if (mAction == null) {
                throw new IllegalStateException("Input ranges must have an associated action.");
            }
            Slice.Builder sb = new Slice.Builder(builder);
            super.apply(sb);
            if (mThumb != null) {
                sb.addIcon(mThumb, null);
            }
            builder.addAction(mAction, sb.build(), SUBTYPE_RANGE).addHints(HINT_LIST_ITEM);
            if (mStartItem != null) {
                builder.addSubSlice(mStartItem);
            }
            for (int i = 0; i < mEndItems.size(); i++) {
                builder.addSubSlice(mEndItems.get(i));
            }
        }
    }

    /**
     * Builder to construct an input range row.
     */
    public static class RatingBuilderImpl extends TemplateBuilderImpl {
        private final PendingIntent mAction;
        protected int mMin = 0;
        protected int mMax = 100;
        protected int mValue = 0;
        @Nullable
        protected CharSequence mTitle;
        @Nullable
        protected CharSequence mSubtitle;
        @Nullable
        protected CharSequence mContentDescr;
        @Nullable
        protected SliceAction mPrimaryAction;
        protected boolean mValueSet = false;
        private Slice mStartItem;

        RatingBuilderImpl(Slice.Builder sb, RatingBuilder builder) {
            super(sb, null);
            mValueSet = builder.isValueSet();
            mMin = builder.getMin();
            mMax = builder.getMax();
            mValue = (int) builder.getValue();
            mTitle = builder.getTitle();
            mSubtitle = builder.getSubtitle();
            mContentDescr = builder.getContentDescription();
            mPrimaryAction = builder.getPrimaryAction();
            mAction = builder.getInputAction();
            if (builder.getTitleIcon() != null) {
                setTitleItem(builder.getTitleIcon(), builder.getTitleImageMode(),
                        builder.isTitleItemLoading());
            }
        }

        void setTitleItem(IconCompat icon, int imageMode, boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder())
                    .addIcon(icon, null, parseImageMode(imageMode, isLoading));
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mStartItem = sb.addHints(HINT_TITLE).build();
        }

        @Override
        public void apply(@NonNull Slice.Builder builder) {
            if (mAction == null) {
                throw new IllegalStateException("Star rating must have an associated action.");
            }

            if (!mValueSet) {
                // Unset, make it outside whatever min is, to represent an unset value
                mValue = mMin - 1;
            }
            if (mTitle != null) {
                builder.addText(mTitle, null, HINT_TITLE);
            }
            if (mSubtitle != null) {
                builder.addText(mSubtitle, null);
            }
            if (mContentDescr != null) {
                builder.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION);
            }
            if (mPrimaryAction != null) {
                mPrimaryAction.setPrimaryAction(builder);
            }
            if (mStartItem != null) {
                builder.addSubSlice(mStartItem);
            }
            Slice.Builder sb = new Slice.Builder(builder);

            sb.addHints(HINT_LIST_ITEM)
                    .addInt(mMin, SUBTYPE_MIN)
                    .addInt(mMax, SUBTYPE_MAX)
                    .addInt(mValue, SUBTYPE_VALUE)
                    .addInt(RANGE_MODE_STAR_RATING, SUBTYPE_RANGE_MODE);
            builder.addAction(mAction, sb.build(), SUBTYPE_RANGE).addHints(HINT_LIST_ITEM);
        }

        boolean hasText() {
            return mTitle != null || mSubtitle != null;
        }
    }

    /**
     *
     */
    public static class RowBuilderImpl extends TemplateBuilderImpl {

        private boolean mIsEndOfSection;
        private SliceAction mPrimaryAction;
        private SliceItem mTitleItem;
        private SliceItem mSubtitleItem;
        private Slice mStartItem;
        private final ArrayList<Slice> mEndItems = new ArrayList<>();
        private CharSequence mContentDescr;

        /**
         */
        private RowBuilderImpl(@NonNull ListBuilderImpl parent) {
            super(parent.createChildBuilder(), null);
        }

        /**
         */
        private RowBuilderImpl(@NonNull Uri uri) {
            super(new Slice.Builder(uri), null);
        }

        /**
         */
        RowBuilderImpl(Slice.Builder builder) {
            super(builder, null);
        }

        @SuppressWarnings("unchecked")
        void fillFrom(RowBuilder builder) {
            if (builder.getUri() != null) {
                setBuilder(new Slice.Builder(builder.getUri()));
            }
            setPrimaryAction(builder.getPrimaryAction());
            mIsEndOfSection = builder.isEndOfSection();
            if (builder.getLayoutDirection() != -1) {
                setLayoutDirection(builder.getLayoutDirection());
            }
            if (builder.getTitleAction() != null || builder.isTitleActionLoading()) {
                setTitleItem(builder.getTitleAction(), builder.isTitleActionLoading());
            } else if (builder.getTitleIcon() != null || builder.isTitleItemLoading()) {
                setTitleItem(builder.getTitleIcon(), builder.getTitleImageMode(),
                        builder.isTitleItemLoading());
            } else if (builder.getTimeStamp() != -1L) {
                setTitleItem(builder.getTimeStamp());
            }
            if (builder.getTitle() != null || builder.isTitleLoading()) {
                setTitle(builder.getTitle(), builder.isTitleLoading());
            }
            if (builder.getSubtitle() != null || builder.isSubtitleLoading()) {
                setSubtitle(builder.getSubtitle(), builder.isSubtitleLoading());
            }
            if (builder.getContentDescription() != null) {
                setContentDescription(builder.getContentDescription());
            }
            List<Object> endItems = builder.getEndItems();
            List<Integer> endTypes = builder.getEndTypes();
            List<Boolean> endLoads = builder.getEndLoads();
            for (int i = 0; i < endItems.size(); i++) {
                switch (endTypes.get(i)) {
                    case RowBuilder.TYPE_TIMESTAMP:
                        addEndItem((Long) endItems.get(i));
                        break;
                    case RowBuilder.TYPE_ACTION:
                        addEndItem((SliceAction) endItems.get(i), endLoads.get(i));
                        break;
                    case RowBuilder.TYPE_ICON:
                        Pair<IconCompat, Integer> pair =
                                (Pair<IconCompat, Integer>) endItems.get(i);
                        addEndItem(pair.first, pair.second, endLoads.get(i));
                        break;
                }
            }
        }

        /**
         */
        private void setTitleItem(long timeStamp) {
            mStartItem = new Slice.Builder(getBuilder())
                    .addTimestamp(timeStamp, null).addHints(HINT_TITLE).build();
        }

        /**
         */
        protected void setTitleItem(@NonNull final IconCompat icon, final int imageMode) {
            setTitleItem(icon, imageMode, false /* isLoading */);
        }

        /**
         */
        private void setTitleItem(@NonNull final IconCompat icon, final int imageMode,
                final boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder())
                    .addIcon(icon, null, parseImageMode(imageMode, isLoading));
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mStartItem = sb.addHints(HINT_TITLE).build();
        }

        /**
         */
        private void setTitleItem(@NonNull final SliceAction action, final boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder()).addHints(HINT_TITLE);
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mStartItem = action.buildSlice(sb);
        }

        /**
         */
        private void setPrimaryAction(@NonNull SliceAction action) {
            mPrimaryAction = action;
        }

        /**
         */
        private void setTitle(@NonNull final CharSequence title, final boolean isLoading) {
            mTitleItem = new SliceItem(title, FORMAT_TEXT, null, new String[] {HINT_TITLE});
            if (isLoading) {
                mTitleItem.addHint(HINT_PARTIAL);
            }
        }

        /**
         */
        protected void setSubtitle(@NonNull final CharSequence subtitle) {
            setSubtitle(subtitle, false /* isLoading */);
        }

        /**
         */
        private void setSubtitle(@NonNull final CharSequence subtitle, final boolean isLoading) {
            mSubtitleItem = new SliceItem(subtitle, FORMAT_TEXT, null, new String[0]);
            if (isLoading) {
                mSubtitleItem.addHint(HINT_PARTIAL);
            }
        }

        /**
         */
        protected void addEndItem(long timeStamp) {
            mEndItems.add(new Slice.Builder(getBuilder()).addTimestamp(timeStamp,
                    null).build());
        }

        /**
         */
        private void addEndItem(@NonNull final IconCompat icon, final int imageMode,
                final boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder())
                    .addIcon(icon, null, parseImageMode(imageMode, isLoading));
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mEndItems.add(sb.build());
        }

        /**
         */
        private void addEndItem(@NonNull final SliceAction action, final boolean isLoading) {
            Slice.Builder sb = new Slice.Builder(getBuilder());
            if (isLoading) {
                sb.addHints(HINT_PARTIAL);
            }
            mEndItems.add(action.buildSlice(sb));
        }

        private void setContentDescription(CharSequence description) {
            mContentDescr = description;
        }

        private void setLayoutDirection(int layoutDirection) {
            getBuilder().addInt(layoutDirection, SUBTYPE_LAYOUT_DIRECTION);
        }

        /**
         */
        public boolean isEndOfSection() {
            return mIsEndOfSection;
        }

        boolean hasText() {
            return mTitleItem != null || mSubtitleItem != null;
        }

        /**
         */
        @Override
        public void apply(@NonNull Slice.Builder b) {
            if (mStartItem != null) {
                b.addSubSlice(mStartItem);
            }
            if (mTitleItem != null) {
                b.addItem(mTitleItem);
            }
            if (mSubtitleItem != null) {
                b.addItem(mSubtitleItem);
            }
            for (int i = 0; i < mEndItems.size(); i++) {
                Slice item = mEndItems.get(i);
                b.addSubSlice(item);
            }
            if (mContentDescr != null) {
                b.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION);
            }
            if (mPrimaryAction != null) {
                mPrimaryAction.setPrimaryAction(b);
            }
        }
    }

    /**
     */
    public static class HeaderBuilderImpl extends TemplateBuilderImpl {

        private SliceItem mTitleItem;
        private SliceItem mSubtitleItem;
        private SliceItem mSummaryItem;
        private SliceAction mPrimaryAction;
        private CharSequence mContentDescr;

        /**
         */
        HeaderBuilderImpl(@NonNull ListBuilderImpl parent) {
            super(parent.createChildBuilder(), null);
        }

        /**
         */
        private HeaderBuilderImpl(@NonNull Uri uri) {
            super(new Slice.Builder(uri), null);
        }

        void fillFrom(HeaderBuilder builder) {
            if (builder.getUri() != null) {
                setBuilder(new Slice.Builder(builder.getUri()));
            }
            setPrimaryAction(builder.getPrimaryAction());
            if (builder.getLayoutDirection() != -1) {
                setLayoutDirection(builder.getLayoutDirection());
            }
            if (builder.getTitle() != null || builder.isTitleLoading()) {
                setTitle(builder.getTitle(), builder.isTitleLoading());
            }
            if (builder.getSubtitle() != null || builder.isSubtitleLoading()) {
                setSubtitle(builder.getSubtitle(), builder.isSubtitleLoading());
            }
            if (builder.getSummary() != null || builder.isSummaryLoading()) {
                setSummary(builder.getSummary(), builder.isSummaryLoading());
            }
            if (builder.getContentDescription() != null) {
                setContentDescription(builder.getContentDescription());
            }
        }

        /**
         */
        @Override
        public void apply(@NonNull Slice.Builder b) {
            if (mTitleItem != null) {
                b.addItem(mTitleItem);
            }
            if (mSubtitleItem != null) {
                b.addItem(mSubtitleItem);
            }
            if (mSummaryItem != null) {
                b.addItem(mSummaryItem);
            }
            if (mContentDescr != null) {
                b.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION);
            }
            if (mPrimaryAction != null) {
                mPrimaryAction.setPrimaryAction(b);
            }
            if (mSubtitleItem == null && mTitleItem == null) {
                throw new IllegalStateException("Header requires a title or subtitle to be set.");
            }
        }

        /**
         */
        private void setTitle(CharSequence title, boolean isLoading) {
            mTitleItem = new SliceItem(title, FORMAT_TEXT, null, new String[] {HINT_TITLE});
            if (isLoading) {
                mTitleItem.addHint(HINT_PARTIAL);
            }
        }

        /**
         */
        private void setSubtitle(CharSequence subtitle, boolean isLoading) {
            mSubtitleItem = new SliceItem(subtitle, FORMAT_TEXT, null, new String[0]);
            if (isLoading) {
                mSubtitleItem.addHint(HINT_PARTIAL);
            }
        }

        /**
         */
        private void setSummary(CharSequence summarySubtitle, boolean isLoading) {
            mSummaryItem = new SliceItem(summarySubtitle, FORMAT_TEXT, null,
                    new String[] {HINT_SUMMARY});
            if (isLoading) {
                mSummaryItem.addHint(HINT_PARTIAL);
            }
        }

        /**
         */
        private void setPrimaryAction(SliceAction action) {
            mPrimaryAction = action;
        }

        /**
         */
        private void setContentDescription(CharSequence description) {
            mContentDescr = description;
        }

        private void setLayoutDirection(int layoutDirection) {
            getBuilder().addInt(layoutDirection, SUBTYPE_LAYOUT_DIRECTION);
        }
    }
}