blob: 8f74db65c8fbc1513558c2f7b313c714c06915d4 [file] [log] [blame]
Jason Monk8a452e92017-10-31 19:21:47 -04001/*
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
Jason Monkdcb5e2f2017-11-15 20:19:43 -050019import static android.app.slice.Slice.HINT_LARGE;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080020import static android.app.slice.Slice.HINT_NO_TINT;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050021import static android.app.slice.Slice.HINT_TITLE;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080022import static android.app.slice.SliceItem.FORMAT_ACTION;
Jason Monk0c76d302017-11-21 14:02:27 -050023import static android.app.slice.SliceItem.FORMAT_IMAGE;
Jason Monk0c76d302017-11-21 14:02:27 -050024import static android.app.slice.SliceItem.FORMAT_TEXT;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080025import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
Jason Monk8a452e92017-10-31 19:21:47 -040026import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
27import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
28
Jason Monk2a7d0fc2017-11-15 10:10:24 -050029import android.annotation.TargetApi;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080030import android.app.PendingIntent;
Jason Monk8a452e92017-10-31 19:21:47 -040031import android.content.Context;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080032import android.content.res.Resources;
Jason Monk8a452e92017-10-31 19:21:47 -040033import android.graphics.Color;
Mady Mellordc052042018-01-04 17:11:07 -080034import android.support.annotation.ColorInt;
Jason Monk8a452e92017-10-31 19:21:47 -040035import android.support.annotation.RestrictTo;
36import android.util.AttributeSet;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080037import android.util.Log;
Mady Mellor238b9b62018-01-09 16:15:40 -080038import android.util.Pair;
Jason Monk8a452e92017-10-31 19:21:47 -040039import android.util.TypedValue;
40import android.view.Gravity;
41import android.view.LayoutInflater;
42import android.view.View;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080043import android.view.ViewGroup;
Jason Monk8a452e92017-10-31 19:21:47 -040044import android.widget.FrameLayout;
45import android.widget.ImageView;
46import android.widget.ImageView.ScaleType;
47import android.widget.LinearLayout;
48import android.widget.TextView;
49
Mady Mellor32206132017-12-21 22:22:26 -080050import java.util.ArrayList;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080051import java.util.Iterator;
Jason Monk8a452e92017-10-31 19:21:47 -040052import java.util.List;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080053import java.util.function.Predicate;
54import java.util.stream.Collectors;
Jason Monk8a452e92017-10-31 19:21:47 -040055
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080056import androidx.app.slice.Slice;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050057import androidx.app.slice.SliceItem;
Jason Monk8a452e92017-10-31 19:21:47 -040058import androidx.app.slice.core.SliceQuery;
59import androidx.app.slice.view.R;
60
61/**
62 * @hide
63 */
64@RestrictTo(RestrictTo.Scope.LIBRARY)
Jason Monk2a7d0fc2017-11-15 10:10:24 -050065@TargetApi(24)
Mady Mellordc052042018-01-04 17:11:07 -080066public class GridRowView extends SliceChildView implements View.OnClickListener {
Jason Monk8a452e92017-10-31 19:21:47 -040067
68 private static final String TAG = "GridView";
69
Mady Mellordc052042018-01-04 17:11:07 -080070 // TODO -- Should add notion to the builder so that apps could define the "see more" intent
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080071 private static final boolean ALLOW_SEE_MORE = false;
72
73 private static final int TITLE_TEXT_LAYOUT = R.layout.abc_slice_title;
74 private static final int TEXT_LAYOUT = R.layout.abc_slice_secondary_text;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080075 // Max number of *just* images that can be shown in a row
Jason Monk8a452e92017-10-31 19:21:47 -040076 private static final int MAX_IMAGES = 3;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080077 // Max number of normal cell items that can be shown in a row
Jason Monk8a452e92017-10-31 19:21:47 -040078 private static final int MAX_ALL = 5;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080079
80 // Max number of text items that can show in a cell
81 private static final int MAX_CELL_TEXT = 2;
82 // Max number of text items that can show in a cell if the mode is small
83 private static final int MAX_CELL_TEXT_SMALL = 1;
84 // Max number of images that can show in a cell
85 private static final int MAX_CELL_IMAGES = 1;
86
Mady Mellor238b9b62018-01-09 16:15:40 -080087 private int mRowIndex;
Jason Monk8a452e92017-10-31 19:21:47 -040088 private boolean mIsAllImages;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080089 private @SliceView.SliceMode int mSliceMode = 0;
90
91 private int mIconSize;
92 private int mLargeIconSize;
93 private int mBigPictureHeight;
94 private int mAllImagesHeight;
Mady Mellordc052042018-01-04 17:11:07 -080095 private GridContent mGridContent;
96 private LinearLayout mViewContainer;
Mady Mellor238b9b62018-01-09 16:15:40 -080097
Mady Mellor71ef84d2017-12-11 13:33:36 -080098 public GridRowView(Context context) {
Mady Mellor5b2c0ce2017-12-08 12:16:37 -080099 this(context, null);
100 }
Jason Monk8a452e92017-10-31 19:21:47 -0400101
Mady Mellor71ef84d2017-12-11 13:33:36 -0800102 public GridRowView(Context context, AttributeSet attrs) {
Jason Monk8a452e92017-10-31 19:21:47 -0400103 super(context, attrs);
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800104 final Resources res = getContext().getResources();
105 mIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
106 mLargeIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_large_icon_size);
107 mBigPictureHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_big_picture_height);
108 mAllImagesHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
Mady Mellordc052042018-01-04 17:11:07 -0800109 mViewContainer = new LinearLayout(getContext());
110 mViewContainer.setOrientation(LinearLayout.HORIZONTAL);
111 addView(mViewContainer, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
Jason Monk8a452e92017-10-31 19:21:47 -0400112 }
113
114 @Override
115 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
116 if (mIsAllImages) {
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800117 int count = getChildCount();
118 int height = (count == 1) ? mBigPictureHeight : mAllImagesHeight;
Jason Monk8a452e92017-10-31 19:21:47 -0400119 heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
120 getLayoutParams().height = height;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800121 for (int i = 0; i < count; i++) {
Jason Monk8a452e92017-10-31 19:21:47 -0400122 getChildAt(i).getLayoutParams().height = height;
123 }
124 }
125 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
126 }
127
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800128 @Override
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800129 public int getMode() {
130 return mSliceMode;
131 }
132
Mady Mellor238b9b62018-01-09 16:15:40 -0800133 @Override
Mady Mellordc052042018-01-04 17:11:07 -0800134 public void setTint(@ColorInt int tintColor) {
135 super.setTint(tintColor);
136 if (mGridContent != null) {
137 // TODO -- could be smarter about this
138 resetView();
139 populateViews(mGridContent);
140 }
Mady Mellor238b9b62018-01-09 16:15:40 -0800141 }
142
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800143 /**
144 * This is called when GridView is being used as a small template.
145 */
146 @Override
147 public void setSlice(Slice slice) {
Mady Mellor238b9b62018-01-09 16:15:40 -0800148 mRowIndex = 0;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800149 mSliceMode = SliceView.MODE_SMALL;
150 Slice.Builder sb = new Slice.Builder(slice.getUri());
151 sb.addSubSlice(slice);
152 Slice parentSlice = sb.build();
Mady Mellordc052042018-01-04 17:11:07 -0800153 mGridContent = new GridContent(parentSlice.getItems().get(0));
154 populateViews(mGridContent);
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800155 }
156
157 /**
158 * This is called when GridView is being used as a component in a large template.
159 */
Jason Monk8a452e92017-10-31 19:21:47 -0400160 @Override
Mady Mellor238b9b62018-01-09 16:15:40 -0800161 public void setSliceItem(SliceItem slice, boolean isHeader, int index,
162 SliceView.SliceObserver observer) {
163 setSliceObserver(observer);
164 mRowIndex = index;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800165 mSliceMode = SliceView.MODE_LARGE;
Mady Mellordc052042018-01-04 17:11:07 -0800166 mGridContent = new GridContent(slice);
167 populateViews(mGridContent);
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800168 }
169
Mady Mellor32206132017-12-21 22:22:26 -0800170 private void populateViews(GridContent gc) {
171 mIsAllImages = gc.isAllImages();
Mady Mellor32206132017-12-21 22:22:26 -0800172 ArrayList<GridContent.CellContent> cells = gc.getGridContent();
Mady Mellor238b9b62018-01-09 16:15:40 -0800173 final int max = mIsAllImages ? MAX_IMAGES : MAX_ALL;
Mady Mellor32206132017-12-21 22:22:26 -0800174 for (int i = 0; i < cells.size(); i++) {
175 if (isFull()) {
176 break;
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800177 }
Mady Mellor238b9b62018-01-09 16:15:40 -0800178 addCell(cells.get(i), i, Math.min(cells.size(), max));
Jason Monk8a452e92017-10-31 19:21:47 -0400179 }
Mady Mellor32206132017-12-21 22:22:26 -0800180 if (ALLOW_SEE_MORE && mIsAllImages && cells.size() > getChildCount()) {
181 addSeeMoreCount(cells.size() - getChildCount());
Jason Monk8a452e92017-10-31 19:21:47 -0400182 }
183 }
184
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800185 private void addSeeMoreCount(int numExtra) {
Jason Monk8a452e92017-10-31 19:21:47 -0400186 View last = getChildAt(getChildCount() - 1);
187 FrameLayout frame = new FrameLayout(getContext());
188 frame.setLayoutParams(last.getLayoutParams());
189
190 removeView(last);
191 frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
192
193 TextView v = new TextView(getContext());
194 v.setTextColor(Color.WHITE);
195 v.setBackgroundColor(0x4d000000);
196 v.setText(getResources().getString(R.string.abc_slice_more_content, numExtra));
197 v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
198 v.setGravity(Gravity.CENTER);
199 frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
200
Mady Mellordc052042018-01-04 17:11:07 -0800201 mViewContainer.addView(frame);
Jason Monk8a452e92017-10-31 19:21:47 -0400202 }
203
204 private boolean isFull() {
205 return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
206 }
207
208 /**
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800209 * Adds a cell to the grid view based on the provided {@link SliceItem}.
Jason Monk8a452e92017-10-31 19:21:47 -0400210 */
Mady Mellor238b9b62018-01-09 16:15:40 -0800211 private void addCell(GridContent.CellContent cell, int index, int total) {
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800212 final int maxCellText = mSliceMode == SliceView.MODE_SMALL
213 ? MAX_CELL_TEXT_SMALL
214 : MAX_CELL_TEXT;
215 LinearLayout cellContainer = new LinearLayout(getContext());
216 cellContainer.setOrientation(LinearLayout.VERTICAL);
217 cellContainer.setGravity(Gravity.CENTER_HORIZONTAL);
Mady Mellor32206132017-12-21 22:22:26 -0800218
219 ArrayList<SliceItem> cellItems = cell.getCellItems();
220 SliceItem contentIntentItem = cell.getContentIntent();
221
222 int textCount = 0;
223 int imageCount = 0;
224 boolean added = false;
225 boolean singleItem = cellItems.size() == 1;
226 List<SliceItem> textItems = null;
227 // In small format we display one text item and prefer titles
228 if (!singleItem && mSliceMode == SliceView.MODE_SMALL) {
229 // Get all our text items
230 textItems = cellItems.stream().filter(new Predicate<SliceItem>() {
231 @Override
232 public boolean test(SliceItem s) {
233 return FORMAT_TEXT.equals(s.getFormat());
234 }
235 }).collect(Collectors.<SliceItem>toList());
236 // If we have more than 1 remove non-titles
237 Iterator<SliceItem> iterator = textItems.iterator();
238 while (textItems.size() > 1) {
239 SliceItem item = iterator.next();
240 if (!item.hasHint(HINT_TITLE)) {
241 iterator.remove();
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800242 }
243 }
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800244 }
Mady Mellor32206132017-12-21 22:22:26 -0800245 for (int i = 0; i < cellItems.size(); i++) {
246 SliceItem item = cellItems.get(i);
247 final String itemFormat = item.getFormat();
248 if (textCount < maxCellText && (FORMAT_TEXT.equals(itemFormat)
249 || FORMAT_TIMESTAMP.equals(itemFormat))) {
250 if (textItems != null && !textItems.contains(item)) {
251 continue;
252 }
Mady Mellordc052042018-01-04 17:11:07 -0800253 if (addItem(item, mTintColor, cellContainer, singleItem)) {
Mady Mellor32206132017-12-21 22:22:26 -0800254 textCount++;
255 added = true;
256 }
257 } else if (imageCount < MAX_CELL_IMAGES && FORMAT_IMAGE.equals(item.getFormat())) {
Mady Mellordc052042018-01-04 17:11:07 -0800258 if (addItem(item, mTintColor, cellContainer, singleItem)) {
Mady Mellor32206132017-12-21 22:22:26 -0800259 imageCount++;
260 added = true;
261 }
262 }
263 }
264 if (added) {
Mady Mellordc052042018-01-04 17:11:07 -0800265 mViewContainer.addView(cellContainer,
266 new LinearLayout.LayoutParams(0, WRAP_CONTENT, 1));
Mady Mellor32206132017-12-21 22:22:26 -0800267 if (contentIntentItem != null) {
Mady Mellor238b9b62018-01-09 16:15:40 -0800268 EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_BUTTON,
269 EventInfo.ROW_TYPE_GRID, mRowIndex);
270 info.setPosition(EventInfo.POSITION_CELL, index, total);
271 Pair<SliceItem, EventInfo> tagItem = new Pair(contentIntentItem, info);
272 cellContainer.setTag(tagItem);
Mady Mellor32206132017-12-21 22:22:26 -0800273 makeClickable(cellContainer);
274 }
275 }
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800276 }
277
278 /**
279 * Adds simple items to a container. Simple items include icons, text, and timestamps.
280 * @return Whether an item was added.
281 */
282 private boolean addItem(SliceItem item, int color, ViewGroup container, boolean singleItem) {
283 final String format = item.getFormat();
284 View addedView = null;
285 if (FORMAT_TEXT.equals(format) || FORMAT_TIMESTAMP.equals(format)) {
286 boolean title = SliceQuery.hasAnyHints(item, HINT_LARGE, HINT_TITLE);
287 TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(title
Mady Mellor32206132017-12-21 22:22:26 -0800288 ? TITLE_TEXT_LAYOUT : TEXT_LAYOUT, null);
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800289 CharSequence text = FORMAT_TIMESTAMP.equals(format)
290 ? SliceViewUtil.getRelativeTimeString(item.getTimestamp())
291 : item.getText();
292 tv.setText(text);
293 container.addView(tv);
294 addedView = tv;
295 } else if (FORMAT_IMAGE.equals(format)) {
296 ImageView iv = new ImageView(getContext());
297 iv.setImageIcon(item.getIcon());
Mady Mellordc052042018-01-04 17:11:07 -0800298 if (color != -1 && !item.hasHint(HINT_NO_TINT) && !item.hasHint(HINT_LARGE)) {
Mady Mellor5b2c0ce2017-12-08 12:16:37 -0800299 iv.setColorFilter(color);
300 }
301 int size = mIconSize;
302 if (item.hasHint(HINT_LARGE)) {
303 iv.setScaleType(ScaleType.CENTER_CROP);
304 size = singleItem ? MATCH_PARENT : mLargeIconSize;
305 }
306 container.addView(iv, new LayoutParams(size, size));
307 addedView = iv;
308 }
309 return addedView != null;
310 }
311
312 private void makeClickable(View layout) {
313 layout.setOnClickListener(this);
314 layout.setBackground(SliceViewUtil.getDrawable(getContext(),
315 android.R.attr.selectableItemBackground));
316 }
317
318 @Override
319 public void onClick(View view) {
Mady Mellor238b9b62018-01-09 16:15:40 -0800320 Pair<SliceItem, EventInfo> tagItem = (Pair<SliceItem, EventInfo>) view.getTag();
321 final SliceItem actionItem = tagItem.first;
322 final EventInfo info = tagItem.second;
323 if (actionItem != null && FORMAT_ACTION.equals(actionItem.getFormat())) {
324 try {
325 actionItem.getAction().send();
326 if (mObserver != null) {
327 mObserver.onSliceAction(info, actionItem);
Jason Monk8a452e92017-10-31 19:21:47 -0400328 }
Mady Mellor238b9b62018-01-09 16:15:40 -0800329 } catch (PendingIntent.CanceledException e) {
330 Log.w(TAG, "PendingIntent for slice cannot be sent", e);
331 }
Jason Monk8a452e92017-10-31 19:21:47 -0400332 }
333 }
Mady Mellordf269702017-12-20 15:40:00 -0800334
335 @Override
336 public void resetView() {
337 mIsAllImages = true;
Mady Mellordc052042018-01-04 17:11:07 -0800338 mGridContent = null;
339 mViewContainer.removeAllViews();
Mady Mellordf269702017-12-20 15:40:00 -0800340 }
Jason Monk8a452e92017-10-31 19:21:47 -0400341}