• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.tv.twopanelsettings.slices.compat.widget;
18 
19 import static android.app.slice.Slice.HINT_ACTIONS;
20 import static android.app.slice.Slice.HINT_HORIZONTAL;
21 import static android.app.slice.Slice.HINT_KEYWORDS;
22 import static android.app.slice.Slice.HINT_LAST_UPDATED;
23 import static android.app.slice.Slice.HINT_LIST_ITEM;
24 import static android.app.slice.Slice.HINT_SEE_MORE;
25 import static android.app.slice.Slice.HINT_SHORTCUT;
26 import static android.app.slice.Slice.HINT_TITLE;
27 import static android.app.slice.Slice.HINT_TTL;
28 import static android.app.slice.SliceItem.FORMAT_ACTION;
29 import static android.app.slice.SliceItem.FORMAT_SLICE;
30 import static android.app.slice.SliceItem.FORMAT_TEXT;
31 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_SELECTION_OPTION;
32 import static com.android.tv.twopanelsettings.slices.compat.widget.SliceViewPolicy.MODE_SMALL;
33 
34 import android.content.Context;
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 import com.android.tv.twopanelsettings.slices.compat.Slice;
38 import com.android.tv.twopanelsettings.slices.compat.SliceItem;
39 import com.android.tv.twopanelsettings.slices.compat.SliceMetadata;
40 import com.android.tv.twopanelsettings.slices.compat.core.SliceAction;
41 import com.android.tv.twopanelsettings.slices.compat.core.SliceActionImpl;
42 import com.android.tv.twopanelsettings.slices.compat.core.SliceQuery;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.List;
46 
47 /** Extracts information required to present content in a list format from a slice. */
48 // @RestrictTo(RestrictTo.Scope.LIBRARY)
49 // @Deprecated // Supported for TV
50 public class ListContent extends SliceContent {
51 
52   private SliceAction mPrimaryAction;
53   private RowContent mHeaderContent;
54   private RowContent mSeeMoreContent;
55   private ArrayList<SliceContent> mRowItems = new ArrayList<>();
56   private List<SliceAction> mSliceActions;
57 
ListContent(@onNull Slice slice)58   public ListContent(@NonNull Slice slice) {
59     super(slice);
60     if (mSliceItem == null) {
61       return;
62     }
63     populate(slice);
64   }
65 
66   // @Deprecated // Supported for TV
ListContent(Context context, @NonNull Slice slice)67   public ListContent(Context context, @NonNull Slice slice) {
68     super(slice);
69     if (mSliceItem == null) {
70       return;
71     }
72     populate(slice);
73   }
74 
populate(Slice slice)75   private void populate(Slice slice) {
76     if (slice == null) {
77       return;
78     }
79     mSliceActions = SliceMetadata.getSliceActions(slice);
80     final SliceItem headerItem = findHeaderItem(slice);
81     if (headerItem != null) {
82       mHeaderContent = new RowContent(headerItem, 0);
83       mRowItems.add(mHeaderContent);
84     }
85     final SliceItem seeMoreItem = getSeeMoreItem(slice);
86     if (seeMoreItem != null) {
87       mSeeMoreContent = new RowContent(seeMoreItem, -1);
88     }
89 
90     // Filter + create row items
91     List<SliceItem> children = slice.getItems();
92     for (int i = 0; i < children.size(); i++) {
93       final SliceItem child = children.get(i);
94       final String format = child.getFormat();
95       boolean isNonRowContent =
96           child.hasAnyHints(
97               HINT_ACTIONS, HINT_SEE_MORE, HINT_KEYWORDS, HINT_TTL, HINT_LAST_UPDATED);
98       if (!isNonRowContent && (FORMAT_ACTION.equals(format) || FORMAT_SLICE.equals(format))) {
99         if (mHeaderContent == null && !child.hasHint(HINT_LIST_ITEM)) {
100           mHeaderContent = new RowContent(child, 0);
101           mRowItems.add(0, mHeaderContent);
102         } else if (child.hasHint(HINT_LIST_ITEM)) {
103           if (child.hasHint(HINT_HORIZONTAL)) {
104             mRowItems.add(new GridContent(child, i));
105           } else {
106             mRowItems.add(new RowContent(child, i));
107           }
108         }
109       }
110     }
111     // Ensure we have something for the header -- use first row
112     if (mHeaderContent == null && mRowItems.size() >= 1) {
113       // We enforce RowContent has first item on builder side; if that changes this
114       // could be an issue
115       mHeaderContent = (RowContent) mRowItems.get(0);
116       mHeaderContent.setIsHeader(true);
117     }
118     if (!mRowItems.isEmpty() && mRowItems.get(mRowItems.size() - 1) instanceof GridContent) {
119       // Grid item is the last item, note that.
120       ((GridContent) mRowItems.get(mRowItems.size() - 1)).setIsLastIndex(true);
121     }
122     mPrimaryAction = findPrimaryAction();
123   }
124 
125   @Override
getHeight(SliceStyle style, SliceViewPolicy policy)126   public int getHeight(SliceStyle style, SliceViewPolicy policy) {
127     return style.getListHeight(this, policy);
128   }
129 
130   /**
131    * Gets the row items to display in this list.
132    *
133    * @param availableHeight the available height for displaying the list.
134    * @param style the style info to use when determining row items to return.
135    * @param policy the policy info (scrolling, mode) to use when determining row items to return.
136    * @return the row items that should be shown based on the provided configuration.
137    */
getRowItems( int availableHeight, SliceStyle style, SliceViewPolicy policy)138   public DisplayedListItems getRowItems(
139       int availableHeight, SliceStyle style, SliceViewPolicy policy) {
140     if (policy.getMode() == MODE_SMALL) {
141       return new DisplayedListItems(
142           new ArrayList<>(Arrays.asList(getHeader())), /* hiddenItemCount= */ mRowItems.size() - 1);
143     } else if (!policy.isScrollable() && availableHeight > 0) {
144       return style.getListItemsForNonScrollingList(this, availableHeight, policy);
145     }
146     return new DisplayedListItems(style.getListItemsToDisplay(this), /* hiddenItemCount= */ 0);
147   }
148 
149   /**
150    * @return whether this list has content that is valid to display.
151    */
152   @Override
isValid()153   public boolean isValid() {
154     return super.isValid() && !mRowItems.isEmpty();
155   }
156 
157   @Nullable
getHeader()158   public RowContent getHeader() {
159     return mHeaderContent;
160   }
161 
162   @Nullable
getSliceActions()163   public List<SliceAction> getSliceActions() {
164     return mSliceActions;
165   }
166 
167   @NonNull
getRowItems()168   public ArrayList<SliceContent> getRowItems() {
169     return mRowItems;
170   }
171 
getSeeMoreItem()172   public SliceContent getSeeMoreItem() {
173     return mSeeMoreContent;
174   }
175 
176   /**
177    * @return the type of template that the header represents.
178    */
getHeaderTemplateType()179   public int getHeaderTemplateType() {
180     return getRowType(mHeaderContent, true, mSliceActions);
181   }
182 
183   @Override
184   @Nullable
getShortcut(@ullable Context context)185   public SliceAction getShortcut(@Nullable Context context) {
186     return mPrimaryAction != null ? mPrimaryAction : super.getShortcut(context);
187   }
188 
189   /** Whether the first row should show title items on the start. */
showTitleItems(boolean enabled)190   public void showTitleItems(boolean enabled) {
191     if (mHeaderContent != null) {
192       mHeaderContent.showTitleItems(enabled);
193     }
194   }
195 
196   /** Whether the header row should show the bottom divider. */
showHeaderDivider(boolean enabled)197   public void showHeaderDivider(boolean enabled) {
198     if (mHeaderContent != null && mRowItems.size() > 1) {
199       mHeaderContent.showBottomDivider(enabled);
200     }
201   }
202 
203   /** Whether all the row contents should show action dividers. */
showActionDividers(boolean enabled)204   public void showActionDividers(boolean enabled) {
205     for (SliceContent item : mRowItems) {
206       if (item instanceof RowContent) {
207         ((RowContent) item).showActionDivider(enabled);
208       }
209     }
210   }
211 
212   /**
213    * @return suitable action to use for a tap on the slice template or for the shortcut.
214    */
215   @Nullable
findPrimaryAction()216   private SliceAction findPrimaryAction() {
217     SliceItem action = null;
218     if (mHeaderContent != null) {
219       action = mHeaderContent.getPrimaryAction();
220     }
221     if (action == null) {
222       String[] hints = new String[] {HINT_SHORTCUT, HINT_TITLE};
223       action = SliceQuery.find(mSliceItem, FORMAT_ACTION, hints, null);
224     }
225     if (action == null) {
226       action = SliceQuery.find(mSliceItem, FORMAT_ACTION, (String) null, null);
227     }
228     return action != null ? new SliceActionImpl(action) : null;
229   }
230 
231   /**
232    * The type of template that the provided row item represents.
233    *
234    * @param content the content to determine the template type of.
235    * @param isHeader whether this row item is used as a header.
236    * @param actions the actions associated with this slice, only matter if this row is the header.
237    * @return the type of template the provided row item represents.
238    */
getRowType(SliceContent content, boolean isHeader, List<SliceAction> actions)239   public static int getRowType(SliceContent content, boolean isHeader, List<SliceAction> actions) {
240     if (content != null) {
241       if (content instanceof GridContent) {
242         return EventInfo.ROW_TYPE_GRID;
243       } else {
244         RowContent rc = (RowContent) content;
245         SliceItem actionItem = rc.getPrimaryAction();
246         SliceAction primaryAction = null;
247         if (actionItem != null) {
248           primaryAction = new SliceActionImpl(actionItem);
249         }
250         if (rc.getRange() != null) {
251           return FORMAT_ACTION.equals(rc.getRange().getFormat())
252               ? EventInfo.ROW_TYPE_SLIDER
253               : EventInfo.ROW_TYPE_PROGRESS;
254         } else if (rc.getSelection() != null) {
255           return EventInfo.ROW_TYPE_SELECTION;
256         } else if (primaryAction != null && primaryAction.isToggle()) {
257           return EventInfo.ROW_TYPE_TOGGLE;
258         } else if (isHeader && actions != null) {
259           for (int i = 0; i < actions.size(); i++) {
260             if (actions.get(i).isToggle()) {
261               return EventInfo.ROW_TYPE_TOGGLE;
262             }
263           }
264           return EventInfo.ROW_TYPE_LIST;
265         } else {
266           return !rc.getToggleItems().isEmpty()
267               ? EventInfo.ROW_TYPE_TOGGLE
268               : EventInfo.ROW_TYPE_LIST;
269         }
270       }
271     }
272     return EventInfo.ROW_TYPE_LIST;
273   }
274 
275   /**
276    * @return the total height of all the rows contained in the provided list.
277    */
getListHeight( List<SliceContent> listItems, SliceStyle style, SliceViewPolicy policy)278   public static int getListHeight(
279       List<SliceContent> listItems, SliceStyle style, SliceViewPolicy policy) {
280     return style.getListItemsHeight(listItems, policy);
281   }
282 
283   @Nullable
findHeaderItem(@onNull Slice slice)284   private static SliceItem findHeaderItem(@NonNull Slice slice) {
285     // See if header is specified
286     String[] nonHints =
287         new String[] {
288           HINT_LIST_ITEM,
289           HINT_SHORTCUT,
290           HINT_ACTIONS,
291           HINT_KEYWORDS,
292           HINT_TTL,
293           HINT_LAST_UPDATED,
294           HINT_HORIZONTAL,
295           HINT_SELECTION_OPTION
296         };
297     SliceItem header = SliceQuery.find(slice, FORMAT_SLICE, null, nonHints);
298     if (header != null && isValidHeader(header)) {
299       return header;
300     }
301     return null;
302   }
303 
304   @Nullable
getSeeMoreItem(@onNull Slice slice)305   private static SliceItem getSeeMoreItem(@NonNull Slice slice) {
306     SliceItem item =
307         SliceQuery.findTopLevelItem(slice, null, null, new String[] {HINT_SEE_MORE}, null);
308     if (item != null) {
309       if (FORMAT_SLICE.equals(item.getFormat())) {
310         List<SliceItem> items = item.getSlice().getItems();
311         if (items.size() == 1 && FORMAT_ACTION.equals(items.get(0).getFormat())) {
312           return items.get(0);
313         }
314         return item;
315       }
316     }
317     return null;
318   }
319 
320   /**
321    * @return whether the provided slice item is a valid header.
322    */
isValidHeader(SliceItem sliceItem)323   private static boolean isValidHeader(SliceItem sliceItem) {
324     if (FORMAT_SLICE.equals(sliceItem.getFormat())
325         && !sliceItem.hasAnyHints(HINT_ACTIONS, HINT_KEYWORDS, HINT_SEE_MORE)) {
326       // Minimum valid header is a slice with text
327       SliceItem item = SliceQuery.find(sliceItem, FORMAT_TEXT, (String) null, null);
328       return item != null;
329     }
330     return false;
331   }
332 }
333