Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.slice.widget;
     18 
     19 import static android.app.slice.Slice.HINT_ACTIONS;
     20 import static android.app.slice.Slice.HINT_SEE_MORE;
     21 import static android.app.slice.Slice.HINT_SHORTCUT;
     22 import static android.app.slice.Slice.HINT_TITLE;
     23 import static android.app.slice.Slice.SUBTYPE_COLOR;
     24 import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION;
     25 import static android.app.slice.SliceItem.FORMAT_ACTION;
     26 import static android.app.slice.SliceItem.FORMAT_IMAGE;
     27 import static android.app.slice.SliceItem.FORMAT_INT;
     28 import static android.app.slice.SliceItem.FORMAT_LONG;
     29 import static android.app.slice.SliceItem.FORMAT_SLICE;
     30 import static android.app.slice.SliceItem.FORMAT_TEXT;
     31 
     32 import static androidx.slice.core.SliceHints.HINT_KEYWORDS;
     33 import static androidx.slice.core.SliceHints.HINT_LAST_UPDATED;
     34 import static androidx.slice.core.SliceHints.HINT_TTL;
     35 import static androidx.slice.core.SliceHints.ICON_IMAGE;
     36 import static androidx.slice.core.SliceHints.LARGE_IMAGE;
     37 import static androidx.slice.core.SliceHints.SMALL_IMAGE;
     38 
     39 import android.app.slice.Slice;
     40 import android.content.Context;
     41 import android.content.res.Resources;
     42 
     43 import androidx.annotation.NonNull;
     44 import androidx.annotation.Nullable;
     45 import androidx.annotation.RestrictTo;
     46 import androidx.slice.SliceItem;
     47 import androidx.slice.core.SliceHints;
     48 import androidx.slice.core.SliceQuery;
     49 import androidx.slice.view.R;
     50 
     51 import java.util.ArrayList;
     52 import java.util.List;
     53 
     54 /**
     55  * Extracts information required to present content in a grid format from a slice.
     56  * @hide
     57  */
     58 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     59 public class GridContent {
     60 
     61     private boolean mAllImages;
     62     private SliceItem mColorItem;
     63     private SliceItem mPrimaryAction;
     64     private ArrayList<CellContent> mGridContent = new ArrayList<>();
     65     private SliceItem mSeeMoreItem;
     66     private int mMaxCellLineCount;
     67     private boolean mHasImage;
     68     private @SliceHints.ImageMode int mLargestImageMode;
     69     private SliceItem mContentDescr;
     70 
     71     private int mBigPicMinHeight;
     72     private int mBigPicMaxHeight;
     73     private int mAllImagesHeight;
     74     private int mImageTextHeight;
     75     private int mMaxHeight;
     76     private int mMinHeight;
     77 
     78     public GridContent(Context context, SliceItem gridItem) {
     79         populate(gridItem);
     80 
     81         Resources res = context.getResources();
     82         mBigPicMinHeight = res.getDimensionPixelSize(R.dimen.abc_slice_big_pic_min_height);
     83         mBigPicMaxHeight = res.getDimensionPixelSize(R.dimen.abc_slice_big_pic_max_height);
     84         mAllImagesHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
     85         mImageTextHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_text_height);
     86         mMinHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_min_height);
     87         mMaxHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_max_height);
     88     }
     89 
     90     /**
     91      * @return whether this grid has content that is valid to display.
     92      */
     93     private boolean populate(SliceItem gridItem) {
     94         mColorItem = SliceQuery.findSubtype(gridItem, FORMAT_INT, SUBTYPE_COLOR);
     95         mSeeMoreItem = SliceQuery.find(gridItem, null, HINT_SEE_MORE, null);
     96         if (mSeeMoreItem != null && FORMAT_SLICE.equals(mSeeMoreItem.getFormat())) {
     97             mSeeMoreItem = mSeeMoreItem.getSlice().getItems().get(0);
     98         }
     99         String[] hints = new String[] {HINT_SHORTCUT, HINT_TITLE};
    100         mPrimaryAction = SliceQuery.find(gridItem, FORMAT_SLICE, hints,
    101                 new String[] {HINT_ACTIONS} /* nonHints */);
    102         mAllImages = true;
    103         if (FORMAT_SLICE.equals(gridItem.getFormat())) {
    104             List<SliceItem> items = gridItem.getSlice().getItems();
    105             if (items.size() == 1 && FORMAT_SLICE.equals(items.get(0).getFormat())) {
    106                 // TODO: this can be removed at release
    107                 items = items.get(0).getSlice().getItems();
    108             }
    109             items = filterAndProcessItems(items);
    110             // Check if it it's only one item that is a slice
    111             if (items.size() == 1 && items.get(0).getFormat().equals(FORMAT_SLICE)) {
    112                 items = items.get(0).getSlice().getItems();
    113             }
    114             for (int i = 0; i < items.size(); i++) {
    115                 SliceItem item = items.get(i);
    116                 if (SUBTYPE_CONTENT_DESCRIPTION.equals(item.getSubType())) {
    117                     mContentDescr = item;
    118                 } else {
    119                     CellContent cc = new CellContent(item);
    120                     processContent(cc);
    121                 }
    122             }
    123         } else {
    124             CellContent cc = new CellContent(gridItem);
    125             processContent(cc);
    126         }
    127         return isValid();
    128     }
    129 
    130     private void processContent(CellContent cc) {
    131         if (cc.isValid()) {
    132             mGridContent.add(cc);
    133             if (!cc.isImageOnly()) {
    134                 mAllImages = false;
    135             }
    136             mMaxCellLineCount = Math.max(mMaxCellLineCount, cc.getTextCount());
    137             mHasImage |= cc.hasImage();
    138             mLargestImageMode = Math.max(mLargestImageMode, cc.getImageMode());
    139         }
    140     }
    141 
    142     /**
    143      * @return the list of cell content for this grid.
    144      */
    145     @NonNull
    146     public ArrayList<CellContent> getGridContent() {
    147         return mGridContent;
    148     }
    149 
    150     /**
    151      * @return the color to tint content in this grid.
    152      */
    153     @Nullable
    154     public SliceItem getColorItem() {
    155         return mColorItem;
    156     }
    157 
    158     /**
    159      * @return the content intent item for this grid.
    160      */
    161     @Nullable
    162     public SliceItem getContentIntent() {
    163         return mPrimaryAction;
    164     }
    165 
    166     /**
    167      * @return the see more item to use when not all items in the grid can be displayed.
    168      */
    169     @Nullable
    170     public SliceItem getSeeMoreItem() {
    171         return mSeeMoreItem;
    172     }
    173 
    174     /**
    175      * @return content description for this row.
    176      */
    177     @Nullable
    178     public CharSequence getContentDescription() {
    179         return mContentDescr != null ? mContentDescr.getText() : null;
    180     }
    181 
    182     /**
    183      * @return whether this grid has content that is valid to display.
    184      */
    185     public boolean isValid() {
    186         return mGridContent.size() > 0;
    187     }
    188 
    189     /**
    190      * @return whether the contents of this grid is just images.
    191      */
    192     public boolean isAllImages() {
    193         return mAllImages;
    194     }
    195 
    196     /**
    197      * Filters non-cell items out of the list of items and finds content description.
    198      */
    199     private List<SliceItem> filterAndProcessItems(List<SliceItem> items) {
    200         List<SliceItem> filteredItems = new ArrayList<>();
    201         for (int i = 0; i < items.size(); i++) {
    202             SliceItem item = items.get(i);
    203             // TODO: This see more can be removed at release
    204             boolean containsSeeMore = SliceQuery.find(item, null, HINT_SEE_MORE, null) != null;
    205             boolean isNonCellContent = containsSeeMore
    206                     || item.hasAnyHints(HINT_SHORTCUT, HINT_SEE_MORE, HINT_KEYWORDS, HINT_TTL,
    207                             HINT_LAST_UPDATED);
    208             if (SUBTYPE_CONTENT_DESCRIPTION.equals(item.getSubType())) {
    209                 mContentDescr = item;
    210             } else if (!isNonCellContent) {
    211                 filteredItems.add(item);
    212             }
    213         }
    214         return filteredItems;
    215     }
    216 
    217     /**
    218      * @return the max number of lines of text in the cells of this grid row.
    219      */
    220     public int getMaxCellLineCount() {
    221         return mMaxCellLineCount;
    222     }
    223 
    224     /**
    225      * @return whether this row contains an image.
    226      */
    227     public boolean hasImage() {
    228         return mHasImage;
    229     }
    230 
    231     /**
    232      * @return the height to display a grid row at when it is used as a small template.
    233      * Does not include padding that might be added by slice view attributes,
    234      * see {@link ListContent#getListHeight(Context, List)}.
    235      */
    236     public int getSmallHeight() {
    237         return getHeight(true /* isSmall */);
    238     }
    239 
    240     /**
    241      * @return the height the content in this template requires to be displayed.
    242      * Does not include padding that might be added by slice view attributes,
    243      * see {@link ListContent#getListHeight(Context, List)}.
    244      */
    245     public int getActualHeight() {
    246         return getHeight(false /* isSmall */);
    247     }
    248 
    249     private int getHeight(boolean isSmall) {
    250         if (!isValid()) {
    251             return 0;
    252         }
    253         if (mAllImages) {
    254             return mGridContent.size() == 1
    255                     ? isSmall ? mBigPicMinHeight : mBigPicMaxHeight
    256                     : mLargestImageMode == ICON_IMAGE ? mMinHeight : mAllImagesHeight;
    257         } else {
    258             boolean twoLines = getMaxCellLineCount() > 1;
    259             boolean hasImage = hasImage();
    260             return (twoLines && !isSmall)
    261                     ? hasImage ? mMaxHeight : mMinHeight
    262                     : mLargestImageMode == ICON_IMAGE ? mMinHeight : mImageTextHeight;
    263         }
    264     }
    265 
    266     /**
    267      * Extracts information required to present content in a cell.
    268      * @hide
    269      */
    270     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    271     public static class CellContent {
    272         private SliceItem mContentIntent;
    273         private ArrayList<SliceItem> mCellItems = new ArrayList<>();
    274         private SliceItem mContentDescr;
    275         private int mTextCount;
    276         private boolean mHasImage;
    277         private int mImageMode = -1;
    278 
    279         public CellContent(SliceItem cellItem) {
    280             populate(cellItem);
    281         }
    282 
    283         /**
    284          * @return whether this row has content that is valid to display.
    285          */
    286         public boolean populate(SliceItem cellItem) {
    287             final String format = cellItem.getFormat();
    288             if (!cellItem.hasHint(HINT_SHORTCUT)
    289                     && (FORMAT_SLICE.equals(format) || FORMAT_ACTION.equals(format))) {
    290                 List<SliceItem> items = cellItem.getSlice().getItems();
    291                 // If we've only got one item that's a slice / action use those items instead
    292                 if (items.size() == 1 && (FORMAT_ACTION.equals(items.get(0).getFormat())
    293                         || FORMAT_SLICE.equals(items.get(0).getFormat()))) {
    294                     mContentIntent = items.get(0);
    295                     items = items.get(0).getSlice().getItems();
    296                 }
    297                 if (FORMAT_ACTION.equals(format)) {
    298                     mContentIntent = cellItem;
    299                 }
    300                 mTextCount = 0;
    301                 int imageCount = 0;
    302                 for (int i = 0; i < items.size(); i++) {
    303                     final SliceItem item = items.get(i);
    304                     final String itemFormat = item.getFormat();
    305                     if (SUBTYPE_CONTENT_DESCRIPTION.equals(item.getSubType())) {
    306                         mContentDescr = item;
    307                     } else if (mTextCount < 2 && (FORMAT_TEXT.equals(itemFormat)
    308                             || FORMAT_LONG.equals(itemFormat))) {
    309                         mTextCount++;
    310                         mCellItems.add(item);
    311                     } else if (imageCount < 1 && FORMAT_IMAGE.equals(item.getFormat())) {
    312                         if (item.hasHint(Slice.HINT_NO_TINT)) {
    313                             mImageMode = item.hasHint(Slice.HINT_LARGE)
    314                                     ? LARGE_IMAGE
    315                                     : SMALL_IMAGE;
    316                         } else {
    317                             mImageMode = ICON_IMAGE;
    318                         }
    319                         imageCount++;
    320                         mHasImage = true;
    321                         mCellItems.add(item);
    322                     }
    323                 }
    324             } else if (isValidCellContent(cellItem)) {
    325                 mCellItems.add(cellItem);
    326             }
    327             return isValid();
    328         }
    329 
    330         /**
    331          * @return the action to activate when this cell is tapped.
    332          */
    333         public SliceItem getContentIntent() {
    334             return mContentIntent;
    335         }
    336 
    337         /**
    338          * @return the slice items to display in this cell.
    339          */
    340         public ArrayList<SliceItem> getCellItems() {
    341             return mCellItems;
    342         }
    343 
    344         /**
    345          * @return whether this is content that is valid to show in a grid cell.
    346          */
    347         private boolean isValidCellContent(SliceItem cellItem) {
    348             final String format = cellItem.getFormat();
    349             boolean isNonCellContent = SUBTYPE_CONTENT_DESCRIPTION.equals(cellItem.getSubType())
    350                     || cellItem.hasAnyHints(HINT_KEYWORDS, HINT_TTL, HINT_LAST_UPDATED);
    351             return !isNonCellContent
    352                     && (FORMAT_TEXT.equals(format)
    353                     || FORMAT_LONG.equals(format)
    354                     || FORMAT_IMAGE.equals(format));
    355         }
    356 
    357         /**
    358          * @return whether this grid has content that is valid to display.
    359          */
    360         public boolean isValid() {
    361             return mCellItems.size() > 0 && mCellItems.size() <= 3;
    362         }
    363 
    364         /**
    365          * @return whether this cell contains just an image.
    366          */
    367         public boolean isImageOnly() {
    368             return mCellItems.size() == 1 && FORMAT_IMAGE.equals(mCellItems.get(0).getFormat());
    369         }
    370 
    371         /**
    372          * @return number of text items in this cell.
    373          */
    374         public int getTextCount() {
    375             return mTextCount;
    376         }
    377 
    378         /**
    379          * @return whether this cell contains an image.
    380          */
    381         public boolean hasImage() {
    382             return mHasImage;
    383         }
    384 
    385         /**
    386          * @return the mode of the image.
    387          */
    388         public int getImageMode() {
    389             return mImageMode;
    390         }
    391 
    392         @Nullable
    393         public CharSequence getContentDescription() {
    394             return mContentDescr != null ? mContentDescr.getText() : null;
    395         }
    396     }
    397 }
    398