blob: a22a37d092959a59792a18517340f95ce391ad84 [file] [log] [blame]
Mady Mellor6b5cd612017-12-14 11:36:59 -08001/*
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
17package androidx.app.slice.widget;
18
Mady Melloraf76b3b2018-02-07 10:31:15 -080019import static android.app.slice.Slice.HINT_ACTIONS;
20import static android.app.slice.Slice.HINT_SHORTCUT;
Mady Mellor96834692018-01-29 13:55:17 -080021import static android.app.slice.Slice.HINT_SUMMARY;
Mady Mellor6b5cd612017-12-14 11:36:59 -080022import static android.app.slice.Slice.HINT_TITLE;
23import static android.app.slice.SliceItem.FORMAT_ACTION;
24import static android.app.slice.SliceItem.FORMAT_IMAGE;
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -050025import static android.app.slice.SliceItem.FORMAT_INT;
Mady Mellor6b5cd612017-12-14 11:36:59 -080026import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
27import static android.app.slice.SliceItem.FORMAT_SLICE;
28import static android.app.slice.SliceItem.FORMAT_TEXT;
29import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
Mady Mellor37bd0712018-02-02 14:07:52 -080030
Amin Shaikh853c11f2018-01-17 09:38:58 -050031import static androidx.app.slice.core.SliceHints.SUBTYPE_RANGE;
Mady Mellor6b5cd612017-12-14 11:36:59 -080032
Mady Mellor8a2763f2018-02-16 13:39:25 -080033import android.content.Context;
Mady Mellor6b5cd612017-12-14 11:36:59 -080034import android.support.annotation.Nullable;
35import android.support.annotation.RestrictTo;
Mady Mellor8a2763f2018-02-16 13:39:25 -080036import android.text.TextUtils;
Mady Mellor6b5cd612017-12-14 11:36:59 -080037import android.util.Log;
38
39import java.util.ArrayList;
40import java.util.List;
41
42import androidx.app.slice.SliceItem;
Mady Mellor6b5cd612017-12-14 11:36:59 -080043import androidx.app.slice.core.SliceQuery;
Mady Mellor8a2763f2018-02-16 13:39:25 -080044import androidx.app.slice.view.R;
Mady Mellor6b5cd612017-12-14 11:36:59 -080045
46/**
47 * Extracts information required to present content in a row format from a slice.
48 * @hide
49 */
50@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
51public class RowContent {
52 private static final String TAG = "RowContent";
53
Mady Melloraf76b3b2018-02-07 10:31:15 -080054 private SliceItem mPrimaryAction;
Mady Mellor6b5cd612017-12-14 11:36:59 -080055 private SliceItem mStartItem;
56 private SliceItem mTitleItem;
57 private SliceItem mSubtitleItem;
Mady Mellor96834692018-01-29 13:55:17 -080058 private SliceItem mSummaryItem;
Mady Mellor6b5cd612017-12-14 11:36:59 -080059 private ArrayList<SliceItem> mEndItems = new ArrayList<>();
Amin Shaikhbfeddba2018-01-10 14:51:13 -050060 private boolean mEndItemsContainAction;
Amin Shaikh853c11f2018-01-17 09:38:58 -050061 private SliceItem mRange;
Mady Mellor96834692018-01-29 13:55:17 -080062 private boolean mIsHeader;
Mady Mellor8a2763f2018-02-16 13:39:25 -080063 private int mLineCount = 0;
64 private int mMaxHeight;
65 private int mMinHeight;
Mady Mellor6b5cd612017-12-14 11:36:59 -080066
Mady Mellor8a2763f2018-02-16 13:39:25 -080067 public RowContent(Context context, SliceItem rowSlice, boolean isHeader) {
Mady Mellor96834692018-01-29 13:55:17 -080068 populate(rowSlice, isHeader);
Mady Mellor8a2763f2018-02-16 13:39:25 -080069 mMaxHeight = context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_max_height);
70 mMinHeight = context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_min_height);
Mady Mellor6b5cd612017-12-14 11:36:59 -080071 }
72
73 /**
Mady Mellor32206132017-12-21 22:22:26 -080074 * Resets the content.
75 */
76 public void reset() {
Mady Melloraf76b3b2018-02-07 10:31:15 -080077 mPrimaryAction = null;
Mady Mellor32206132017-12-21 22:22:26 -080078 mStartItem = null;
79 mTitleItem = null;
80 mSubtitleItem = null;
Mady Mellor32206132017-12-21 22:22:26 -080081 mEndItems.clear();
Mady Mellor96834692018-01-29 13:55:17 -080082 mIsHeader = false;
Mady Mellor8a2763f2018-02-16 13:39:25 -080083 mLineCount = 0;
Mady Mellor32206132017-12-21 22:22:26 -080084 }
85
86 /**
Mady Mellor6b5cd612017-12-14 11:36:59 -080087 * @return whether this row has content that is valid to display.
88 */
Mady Mellor8a2763f2018-02-16 13:39:25 -080089 private boolean populate(SliceItem rowSlice, boolean isHeader) {
Mady Mellor32206132017-12-21 22:22:26 -080090 reset();
Mady Mellor96834692018-01-29 13:55:17 -080091 mIsHeader = isHeader;
Mady Mellor6b5cd612017-12-14 11:36:59 -080092 if (!isValidRow(rowSlice)) {
93 Log.w(TAG, "Provided SliceItem is invalid for RowContent");
94 return false;
95 }
Mady Melloraf76b3b2018-02-07 10:31:15 -080096 // Find primary action first (otherwise filtered out of valid row items)
97 String[] hints = new String[] {HINT_SHORTCUT, HINT_TITLE};
98 mPrimaryAction = SliceQuery.find(rowSlice, FORMAT_SLICE, hints,
99 new String[] { HINT_ACTIONS } /* nonHints */);
100
Mady Mellor6b5cd612017-12-14 11:36:59 -0800101 // Filter anything not viable for displaying in a row
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500102 ArrayList<SliceItem> rowItems = filterInvalidItems(rowSlice);
Mady Mellor6b5cd612017-12-14 11:36:59 -0800103 // If we've only got one item that's a slice / action use those items instead
104 if (rowItems.size() == 1 && (FORMAT_ACTION.equals(rowItems.get(0).getFormat())
Mady Melloraf76b3b2018-02-07 10:31:15 -0800105 || FORMAT_SLICE.equals(rowItems.get(0).getFormat()))
106 && !rowItems.get(0).hasHint(HINT_SHORTCUT)) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800107 if (isValidRow(rowItems.get(0))) {
108 rowSlice = rowItems.get(0);
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500109 rowItems = filterInvalidItems(rowSlice);
Mady Mellor6b5cd612017-12-14 11:36:59 -0800110 }
111 }
Amin Shaikh853c11f2018-01-17 09:38:58 -0500112 if (SUBTYPE_RANGE.equals(rowSlice.getSubType())) {
113 mRange = rowSlice;
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500114 }
Mady Mellor6b5cd612017-12-14 11:36:59 -0800115 if (rowItems.size() > 0) {
116 // Start item
Mady Melloraf76b3b2018-02-07 10:31:15 -0800117 SliceItem firstItem = rowItems.get(0);
118 if (FORMAT_SLICE.equals(firstItem.getFormat())) {
119 SliceItem unwrappedItem = firstItem.getSlice().getItems().get(0);
120 if (isStartType(unwrappedItem)) {
121 mStartItem = unwrappedItem;
122 rowItems.remove(0);
123 }
Mady Mellor6b5cd612017-12-14 11:36:59 -0800124 }
Mady Melloraf76b3b2018-02-07 10:31:15 -0800125
Mady Mellor6b5cd612017-12-14 11:36:59 -0800126 // Text + end items
Mady Mellor238b9b62018-01-09 16:15:40 -0800127 ArrayList<SliceItem> endItems = new ArrayList<>();
Mady Mellor6b5cd612017-12-14 11:36:59 -0800128 for (int i = 0; i < rowItems.size(); i++) {
129 final SliceItem item = rowItems.get(i);
130 if (FORMAT_TEXT.equals(item.getFormat())) {
131 if ((mTitleItem == null || !mTitleItem.hasHint(HINT_TITLE))
Mady Mellor96834692018-01-29 13:55:17 -0800132 && item.hasHint(HINT_TITLE) && !item.hasHint(HINT_SUMMARY)) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800133 mTitleItem = item;
Mady Mellor96834692018-01-29 13:55:17 -0800134 } else if (mSubtitleItem == null && !item.hasHint(HINT_SUMMARY)) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800135 mSubtitleItem = item;
Mady Mellor96834692018-01-29 13:55:17 -0800136 } else if (mSummaryItem == null && item.hasHint(HINT_SUMMARY)) {
137 mSummaryItem = item;
Mady Mellor6b5cd612017-12-14 11:36:59 -0800138 }
139 } else {
Mady Mellor238b9b62018-01-09 16:15:40 -0800140 endItems.add(item);
141 }
142 }
Mady Mellor8a2763f2018-02-16 13:39:25 -0800143 if (hasText(mTitleItem)) {
144 mLineCount++;
145 }
146 if (hasText(mSubtitleItem)) {
147 mLineCount++;
148 }
Mady Mellor238b9b62018-01-09 16:15:40 -0800149 // Special rules for end items: only one timestamp, can't be mixture of icons / actions
150 boolean hasTimestamp = mStartItem != null
151 && FORMAT_TIMESTAMP.equals(mStartItem.getFormat());
152 String desiredFormat = null;
153 for (int i = 0; i < endItems.size(); i++) {
154 final SliceItem item = endItems.get(i);
Mady Melloraf76b3b2018-02-07 10:31:15 -0800155 boolean isAction = FORMAT_SLICE.equals(item.getFormat())
156 && item.hasHint(HINT_SHORTCUT);
Mady Mellor238b9b62018-01-09 16:15:40 -0800157 if (FORMAT_TIMESTAMP.equals(item.getFormat())) {
158 if (!hasTimestamp) {
159 hasTimestamp = true;
160 mEndItems.add(item);
161 }
162 } else if (desiredFormat == null) {
163 desiredFormat = item.getFormat();
164 mEndItems.add(item);
Mady Melloraf76b3b2018-02-07 10:31:15 -0800165 mEndItemsContainAction |= isAction;
Mady Mellor238b9b62018-01-09 16:15:40 -0800166 } else if (desiredFormat.equals(item.getFormat())) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800167 mEndItems.add(item);
Mady Melloraf76b3b2018-02-07 10:31:15 -0800168 mEndItemsContainAction |= isAction;
Mady Mellor6b5cd612017-12-14 11:36:59 -0800169 }
170 }
171 }
Mady Mellor6b5cd612017-12-14 11:36:59 -0800172 return isValid();
173 }
174
Mady Mellor6b5cd612017-12-14 11:36:59 -0800175 /**
Amin Shaikh853c11f2018-01-17 09:38:58 -0500176 * @return the {@link SliceItem} representing the range in the row; can be null.
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500177 */
178 @Nullable
Amin Shaikh853c11f2018-01-17 09:38:58 -0500179 public SliceItem getRange() {
180 return mRange;
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500181 }
182
183 /**
Mady Mellor6b5cd612017-12-14 11:36:59 -0800184 * @return whether this row has content that is valid to display.
185 */
186 public boolean isValid() {
187 return mStartItem != null
188 || mTitleItem != null
189 || mSubtitleItem != null
190 || mEndItems.size() > 0;
191 }
192
193 @Nullable
Mady Melloraf76b3b2018-02-07 10:31:15 -0800194 public SliceItem getPrimaryAction() {
195 return mPrimaryAction;
Mady Mellor6b5cd612017-12-14 11:36:59 -0800196 }
197
198 @Nullable
199 public SliceItem getStartItem() {
Mady Mellor96834692018-01-29 13:55:17 -0800200 return mIsHeader ? null : mStartItem;
Mady Mellor6b5cd612017-12-14 11:36:59 -0800201 }
202
203 @Nullable
204 public SliceItem getTitleItem() {
205 return mTitleItem;
206 }
207
208 @Nullable
209 public SliceItem getSubtitleItem() {
210 return mSubtitleItem;
211 }
212
Mady Mellor96834692018-01-29 13:55:17 -0800213 @Nullable
214 public SliceItem getSummaryItem() {
215 return mSummaryItem == null ? mSubtitleItem : mSummaryItem;
216 }
217
Mady Mellor6b5cd612017-12-14 11:36:59 -0800218 public ArrayList<SliceItem> getEndItems() {
219 return mEndItems;
220 }
221
222 /**
Mady Melloraf76b3b2018-02-07 10:31:15 -0800223 * @return whether {@link #getEndItems()} contains a SliceItem with FORMAT_SLICE, HINT_SHORTCUT
Amin Shaikhbfeddba2018-01-10 14:51:13 -0500224 */
225 public boolean endItemsContainAction() {
226 return mEndItemsContainAction;
227 }
228
229 /**
Mady Mellor8a2763f2018-02-16 13:39:25 -0800230 * @return the number of lines of text contained in this row.
231 */
232 public int getLineCount() {
233 return mLineCount;
234 }
235
236 /**
237 * @return the height to display a row at when it is used as a small template.
238 */
239 public int getSmallHeight() {
240 return mMaxHeight;
241 }
242
243 /**
244 * @return the height the content in this template requires to be displayed.
245 */
246 public int getActualHeight() {
247 return isValid()
248 ? (getLineCount() > 1 || mIsHeader) ? mMaxHeight : mMinHeight
249 : 0;
250 }
251
252 private static boolean hasText(SliceItem textSlice) {
253 return textSlice != null && !TextUtils.isEmpty(textSlice.getText());
254 }
255
256 /**
Mady Mellor6b5cd612017-12-14 11:36:59 -0800257 * @return whether this is a valid item to use to populate a row of content.
258 */
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500259 private static boolean isValidRow(SliceItem rowSlice) {
Mady Mellor96834692018-01-29 13:55:17 -0800260 if (rowSlice == null) {
261 return false;
262 }
Mady Mellor6b5cd612017-12-14 11:36:59 -0800263 // Must be slice or action
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500264 if (FORMAT_SLICE.equals(rowSlice.getFormat())
265 || FORMAT_ACTION.equals(rowSlice.getFormat())) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800266 // Must have at least one legitimate child
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500267 List<SliceItem> rowItems = rowSlice.getSlice().getItems();
Mady Mellor6b5cd612017-12-14 11:36:59 -0800268 for (int i = 0; i < rowItems.size(); i++) {
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500269 if (isValidRowContent(rowSlice, rowItems.get(i))) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800270 return true;
271 }
272 }
273 }
Mady Mellor6b5cd612017-12-14 11:36:59 -0800274 return false;
275 }
276
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500277 private static ArrayList<SliceItem> filterInvalidItems(SliceItem rowSlice) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800278 ArrayList<SliceItem> filteredList = new ArrayList<>();
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500279 for (SliceItem i : rowSlice.getSlice().getItems()) {
280 if (isValidRowContent(rowSlice, i)) {
Mady Mellor6b5cd612017-12-14 11:36:59 -0800281 filteredList.add(i);
282 }
283 }
284 return filteredList;
285 }
286
287 /**
Mady Melloraf76b3b2018-02-07 10:31:15 -0800288 * @return whether this item is valid content to display in a row.
Mady Mellor6b5cd612017-12-14 11:36:59 -0800289 */
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500290 private static boolean isValidRowContent(SliceItem slice, SliceItem item) {
Mady Melloraf76b3b2018-02-07 10:31:15 -0800291 if (FORMAT_SLICE.equals(item.getFormat()) && !item.hasHint(HINT_SHORTCUT)) {
292 // Unpack contents of slice
293 item = item.getSlice().getItems().get(0);
294 }
Mady Mellor6b5cd612017-12-14 11:36:59 -0800295 final String itemFormat = item.getFormat();
Mady Mellor6b5cd612017-12-14 11:36:59 -0800296 return FORMAT_TEXT.equals(itemFormat)
297 || FORMAT_IMAGE.equals(itemFormat)
298 || FORMAT_TIMESTAMP.equals(itemFormat)
299 || FORMAT_REMOTE_INPUT.equals(itemFormat)
Mady Melloraf76b3b2018-02-07 10:31:15 -0800300 || (FORMAT_SLICE.equals(itemFormat) && item.hasHint(HINT_TITLE)
301 && !item.hasHint(HINT_SHORTCUT))
302 || (FORMAT_SLICE.equals(itemFormat) && item.hasHint(HINT_SHORTCUT)
303 && !item.hasHint(HINT_TITLE))
Amin Shaikhf2cdc3a2018-01-17 09:35:23 -0500304 || FORMAT_ACTION.equals(itemFormat)
Amin Shaikh853c11f2018-01-17 09:38:58 -0500305 || (FORMAT_INT.equals(itemFormat) && SUBTYPE_RANGE.equals(slice.getSubType()));
Mady Mellor6b5cd612017-12-14 11:36:59 -0800306 }
307
308 /**
309 * @return Whether this item is appropriate to be considered a "start" item, i.e. go in the
310 * front slot of a row.
311 */
312 private static boolean isStartType(SliceItem item) {
313 final String type = item.getFormat();
Mady Melloraf76b3b2018-02-07 10:31:15 -0800314 return (FORMAT_ACTION.equals(type) && (SliceQuery.find(item, FORMAT_IMAGE) != null))
Mady Mellor96834692018-01-29 13:55:17 -0800315 || FORMAT_IMAGE.equals(type)
Mady Melloraf76b3b2018-02-07 10:31:15 -0800316 || FORMAT_TIMESTAMP.equals(type);
Mady Mellor6b5cd612017-12-14 11:36:59 -0800317 }
318}