All Downloads are FREE. Search and download functionalities are using the official Maven repository.

androidx.slice.widget.ListContent Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 14-robolectric-10818077
Show newest version
/*
 * 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.widget;

import static android.app.slice.Slice.HINT_ACTIONS;
import static android.app.slice.Slice.HINT_HORIZONTAL;
import static android.app.slice.Slice.HINT_LIST_ITEM;
import static android.app.slice.Slice.HINT_SEE_MORE;
import static android.app.slice.Slice.HINT_SHORTCUT;
import static android.app.slice.Slice.SUBTYPE_COLOR;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_INT;
import static android.app.slice.SliceItem.FORMAT_SLICE;
import static android.app.slice.SliceItem.FORMAT_TEXT;

import static androidx.slice.core.SliceHints.HINT_KEYWORDS;
import static androidx.slice.core.SliceHints.HINT_LAST_UPDATED;
import static androidx.slice.core.SliceHints.HINT_TTL;
import static androidx.slice.widget.SliceView.MODE_LARGE;
import static androidx.slice.widget.SliceView.MODE_SMALL;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.SliceMetadata;
import androidx.slice.core.SliceAction;
import androidx.slice.core.SliceActionImpl;
import androidx.slice.core.SliceQuery;
import androidx.slice.view.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Extracts information required to present content in a list format from a slice.
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ListContent {

    private Slice mSlice;
    private SliceItem mHeaderItem;
    private SliceItem mColorItem;
    private SliceItem mSeeMoreItem;
    private ArrayList mRowItems = new ArrayList<>();
    private List mSliceActions;
    private Context mContext;

    private int mHeaderTitleSize;
    private int mHeaderSubtitleSize;
    private int mVerticalHeaderTextPadding;
    private int mTitleSize;
    private int mSubtitleSize;
    private int mVerticalTextPadding;
    private int mGridTitleSize;
    private int mGridSubtitleSize;
    private int mVerticalGridTextPadding;
    private int mGridTopPadding;
    private int mGridBottomPadding;

    public ListContent(Context context, Slice slice, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        mSlice = slice;
        mContext = context;

        // TODO: duplicated code from SliceChildView; could do something better
        // Some of this information will impact the size calculations for slice content.
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SliceView,
                defStyleAttr, defStyleRes);
        try {
            mHeaderTitleSize = (int) a.getDimension(
                    R.styleable.SliceView_headerTitleSize, 0);
            mHeaderSubtitleSize = (int) a.getDimension(
                    R.styleable.SliceView_headerSubtitleSize, 0);
            mVerticalHeaderTextPadding = (int) a.getDimension(
                    R.styleable.SliceView_headerTextVerticalPadding, 0);

            mTitleSize = (int) a.getDimension(R.styleable.SliceView_titleSize, 0);
            mSubtitleSize = (int) a.getDimension(
                    R.styleable.SliceView_subtitleSize, 0);
            mVerticalTextPadding = (int) a.getDimension(
                    R.styleable.SliceView_textVerticalPadding, 0);

            mGridTitleSize = (int) a.getDimension(R.styleable.SliceView_gridTitleSize, 0);
            mGridSubtitleSize = (int) a.getDimension(
                    R.styleable.SliceView_gridSubtitleSize, 0);
            int defaultVerticalGridPadding = context.getResources().getDimensionPixelSize(
                    R.dimen.abc_slice_grid_text_inner_padding);
            mVerticalGridTextPadding = (int) a.getDimension(
                    R.styleable.SliceView_gridTextVerticalPadding, defaultVerticalGridPadding);
            mGridTopPadding = (int) a.getDimension(R.styleable.SliceView_gridTopPadding, 0);
            mGridBottomPadding = (int) a.getDimension(R.styleable.SliceView_gridTopPadding, 0);
        } finally {
            a.recycle();
        }

        populate(slice);
    }

    /**
     * @return whether this row has content that is valid to display.
     */
    private boolean populate(Slice slice) {
        mColorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
        // Find slice actions
        mSliceActions = SliceMetadata.getSliceActions(slice);
        // Find header
        mHeaderItem = findHeaderItem(slice);
        if (mHeaderItem != null) {
            mRowItems.add(mHeaderItem);
        }
        mSeeMoreItem = getSeeMoreItem(slice);
        // Filter + create row items
        List children = slice.getItems();
        for (int i = 0; i < children.size(); i++) {
            final SliceItem child = children.get(i);
            final String format = child.getFormat();
            boolean isNonRowContent = child.hasAnyHints(HINT_ACTIONS, HINT_SEE_MORE, HINT_KEYWORDS,
                    HINT_TTL, HINT_LAST_UPDATED);
            if (!isNonRowContent && (FORMAT_ACTION.equals(format) || FORMAT_SLICE.equals(format))) {
                if (mHeaderItem == null && !child.hasHint(HINT_LIST_ITEM)) {
                    mHeaderItem = child;
                    mRowItems.add(0, child);
                } else if (child.hasHint(HINT_LIST_ITEM)) {
                    mRowItems.add(child);
                }
            }
        }
        // Ensure we have something for the header -- use first row
        if (mHeaderItem == null && mRowItems.size() >= 1) {
            mHeaderItem = mRowItems.get(0);
        }
        return isValid();
    }

    /**
     * Expects the provided list of items to be filtered (i.e. only things that can be turned into
     * GridContent or RowContent) and in order (i.e. first item could be a header).
     *
     * @return the total height of all the rows contained in the provided list.
     */
    public int getListHeight(Context context, List listItems) {
        if (listItems == null) {
            return 0;
        }
        int height = 0;
        boolean hasRealHeader = false;
        SliceItem maybeHeader = null;
        if (!listItems.isEmpty()) {
            maybeHeader = listItems.get(0);
            hasRealHeader = !maybeHeader.hasAnyHints(HINT_LIST_ITEM, HINT_HORIZONTAL);
        }
        if (listItems.size() == 1 && !maybeHeader.hasHint(HINT_HORIZONTAL)) {
            return getHeight(context, maybeHeader, true /* isHeader */, 0, 1, MODE_LARGE);
        }
        int rowCount = listItems.size();
        for (int i = 0; i < listItems.size(); i++) {
            height += getHeight(context, listItems.get(i), i == 0 && hasRealHeader /* isHeader */,
                    i, rowCount, MODE_LARGE);
        }
        return height;
    }

    /**
     * Returns a list of items that can be displayed in the provided height. If this list
     * has a {@link #getSeeMoreItem()} this will be returned in the list if appropriate.
     *
     * @param height the height to restrict the items, -1 to use default sizings for non-scrolling
     *               templates.
     * @return the list of items that can be displayed in the provided  height.
     */
    @NonNull
    public List getItemsForNonScrollingList(int height) {
        ArrayList visibleItems = new ArrayList<>();
        if (mRowItems == null || mRowItems.size() == 0) {
            return visibleItems;
        }
        final int idealItemCount = hasHeader() ? 4 : 3;
        final int minItemCount = hasHeader() ? 2 : 1;
        int visibleHeight = 0;
        // Need to show see more
        if (mSeeMoreItem != null) {
            RowContent rc = new RowContent(mContext, mSeeMoreItem, false /* isHeader */);
            visibleHeight += rc.getActualHeight();
        }
        int rowCount = mRowItems.size();
        for (int i = 0; i < rowCount; i++) {
            int itemHeight = getHeight(mContext, mRowItems.get(i), i == 0 /* isHeader */,
                    i, rowCount, MODE_LARGE);
            if ((height == -1 && i > idealItemCount)
                    || (height > 0 && visibleHeight + itemHeight > height)) {
                break;
            } else {
                visibleHeight += itemHeight;
                visibleItems.add(mRowItems.get(i));
            }
        }
        if (mSeeMoreItem != null && visibleItems.size() >= minItemCount) {
            // Only add see more if we're at least showing one item and it's not the header
            visibleItems.add(mSeeMoreItem);
        }
        if (visibleItems.size() == 0) {
            // Didn't have enough space to show anything; should still show something
            visibleItems.add(mRowItems.get(0));
        }
        return visibleItems;
    }

    /**
     * Determines the height of the provided {@link SliceItem}.
     */
    public int getHeight(Context context, SliceItem item, boolean isHeader, int index,
            int count, int mode) {
        if (item.hasHint(HINT_HORIZONTAL)) {
            GridContent gc = new GridContent(context, item);
            int topPadding = gc.isAllImages() && index == 0 ? mGridTopPadding : 0;
            int bottomPadding = gc.isAllImages() && index == count - 1 ? mGridBottomPadding : 0;
            int height = mode == MODE_SMALL ? gc.getSmallHeight() : gc.getActualHeight();
            return height + topPadding + bottomPadding;
        } else {
            RowContent rc = new RowContent(context, item, isHeader);
            return mode == MODE_SMALL ? rc.getSmallHeight() : rc.getActualHeight();
        }
    }

    /**
     * @return whether this list has content that is valid to display.
     */
    public boolean isValid() {
        return mRowItems.size() > 0;
    }

    @Nullable
    public Slice getSlice() {
        return mSlice;
    }

    @Nullable
    public SliceItem getColorItem() {
        return mColorItem;
    }

    @Nullable
    public SliceItem getHeaderItem() {
        return mHeaderItem;
    }

    @Nullable
    public List getSliceActions() {
        return mSliceActions;
    }

    @Nullable
    public SliceItem getSeeMoreItem() {
        return mSeeMoreItem;
    }

    @NonNull
    public ArrayList getRowItems() {
        return mRowItems;
    }

    /**
     * @return whether this list has an explicit header (i.e. row item without HINT_LIST_ITEM)
     */
    public boolean hasHeader() {
        return mHeaderItem != null && isValidHeader(mHeaderItem);
    }

    /**
     * @return the type of template that the header represents.
     */
    public int getHeaderTemplateType() {
        return getRowType(mContext, mHeaderItem, true, mSliceActions);
    }

    /**
     * The type of template that the provided row item represents.
     *
     * @param context context used for this slice.
     * @param rowItem the row item to determine the template type of.
     * @param isHeader whether this row item is used as a header.
     * @param actions the actions associated with this slice, only matter if this row is the header.
     * @return the type of template the provided row item represents.
     */
    public static int getRowType(Context context, SliceItem rowItem, boolean isHeader,
                                 List actions) {
        if (rowItem != null) {
            if (rowItem.hasHint(HINT_HORIZONTAL)) {
                return EventInfo.ROW_TYPE_GRID;
            } else {
                RowContent rc = new RowContent(context, rowItem, isHeader);
                SliceItem actionItem = rc.getPrimaryAction();
                SliceAction primaryAction = null;
                if (actionItem != null) {
                    primaryAction = new SliceActionImpl(actionItem);
                }
                if (rc.getRange() != null) {
                    return FORMAT_ACTION.equals(rc.getRange().getFormat())
                            ? EventInfo.ROW_TYPE_SLIDER
                            : EventInfo.ROW_TYPE_PROGRESS;
                } else if (primaryAction != null && primaryAction.isToggle()) {
                    return EventInfo.ROW_TYPE_TOGGLE;
                } else if (isHeader && actions != null) {
                    for (int i = 0; i < actions.size(); i++) {
                        if (new SliceActionImpl(actions.get(i)).isToggle()) {
                            return EventInfo.ROW_TYPE_TOGGLE;
                        }
                    }
                    return EventInfo.ROW_TYPE_LIST;
                } else {
                    return rc.getToggleItems().size() > 0
                            ? EventInfo.ROW_TYPE_TOGGLE
                            : EventInfo.ROW_TYPE_LIST;
                }
            }
        }
        return EventInfo.ROW_TYPE_LIST;
    }

    /**
     * @return the primary action for this list; i.e. action on the header or first row.
     */
    @Nullable
    public SliceItem getPrimaryAction() {
        if (mHeaderItem != null) {
            if (mHeaderItem.hasHint(HINT_HORIZONTAL)) {
                GridContent gc = new GridContent(mContext, mHeaderItem);
                return gc.getContentIntent();
            } else {
                RowContent rc = new RowContent(mContext, mHeaderItem, false);
                return rc.getPrimaryAction();
            }
        }
        return null;
    }

    @Nullable
    private static SliceItem findHeaderItem(@NonNull Slice slice) {
        // See if header is specified
        String[] nonHints = new String[] {HINT_LIST_ITEM, HINT_SHORTCUT, HINT_ACTIONS,
                HINT_KEYWORDS, HINT_TTL, HINT_LAST_UPDATED, HINT_HORIZONTAL};
        SliceItem header = SliceQuery.find(slice, FORMAT_SLICE, null, nonHints);
        if (header != null && isValidHeader(header)) {
            return header;
        }
        return null;
    }

    @Nullable
    private static SliceItem getSeeMoreItem(@NonNull Slice slice) {
        SliceItem item = SliceQuery.find(slice, null, HINT_SEE_MORE, null);
        if (item != null) {
            if (FORMAT_SLICE.equals(item.getFormat())) {
                List items = item.getSlice().getItems();
                if (items.size() == 1 && FORMAT_ACTION.equals(items.get(0).getFormat())) {
                    return items.get(0);
                }
                return item;
            }
        }
        return null;
    }

    /**
     * @return whether the provided slice item is a valid header.
     */
    public static boolean isValidHeader(SliceItem sliceItem) {
        if (FORMAT_SLICE.equals(sliceItem.getFormat()) && !sliceItem.hasAnyHints(HINT_LIST_ITEM,
                HINT_ACTIONS, HINT_KEYWORDS, HINT_SEE_MORE)) {
             // Minimum valid header is a slice with text
            SliceItem item = SliceQuery.find(sliceItem, FORMAT_TEXT, (String) null, null);
            return item != null;
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy