Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 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.app.slice.widget; |
| 18 | |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 19 | import static android.app.slice.Slice.HINT_LIST; |
| 20 | import static android.app.slice.Slice.HINT_LIST_ITEM; |
| 21 | import static android.app.slice.Slice.HINT_NO_TINT; |
| 22 | import static android.app.slice.Slice.HINT_SELECTED; |
| 23 | import static android.app.slice.Slice.HINT_TITLE; |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 24 | import static android.app.slice.SliceItem.FORMAT_ACTION; |
| 25 | import static android.app.slice.SliceItem.FORMAT_COLOR; |
| 26 | import static android.app.slice.SliceItem.FORMAT_IMAGE; |
| 27 | import static android.app.slice.SliceItem.FORMAT_SLICE; |
| 28 | import static android.app.slice.SliceItem.FORMAT_TEXT; |
| 29 | import static android.app.slice.SliceItem.FORMAT_TIMESTAMP; |
| 30 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 31 | import static androidx.app.slice.core.SliceHints.EXTRA_TOGGLE_STATE; |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 32 | import static androidx.app.slice.core.SliceHints.HINT_SUMMARY; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 33 | |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 34 | import android.annotation.TargetApi; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 35 | import android.app.PendingIntent; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 36 | import android.app.PendingIntent.CanceledException; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 37 | import android.content.Context; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 38 | import android.content.Intent; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 39 | import android.graphics.drawable.Icon; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 40 | import android.os.AsyncTask; |
| 41 | import android.support.annotation.RestrictTo; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 42 | import android.util.Log; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 43 | import android.view.View; |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 44 | import android.widget.CompoundButton; |
Mady Mellor | 5b2c0ce | 2017-12-08 12:16:37 -0800 | [diff] [blame] | 45 | import android.widget.FrameLayout; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 46 | import android.widget.ImageView; |
| 47 | import android.widget.LinearLayout; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 48 | import android.widget.Switch; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 49 | import android.widget.TextView; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 50 | import android.widget.ToggleButton; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 51 | |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 52 | import java.util.ArrayList; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 53 | import java.util.List; |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 54 | import java.util.function.Predicate; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 55 | |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 56 | import androidx.app.slice.Slice; |
| 57 | import androidx.app.slice.SliceItem; |
Jason Monk | 8b099f2 | 2017-11-15 15:15:48 -0500 | [diff] [blame] | 58 | import androidx.app.slice.core.SliceHints; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 59 | import androidx.app.slice.core.SliceQuery; |
| 60 | import androidx.app.slice.view.R; |
| 61 | |
| 62 | /** |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 63 | * Row item is in small template format and can be used to construct list items for use |
| 64 | * with {@link LargeTemplateView}. |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 65 | * |
| 66 | * @hide |
| 67 | */ |
| 68 | @RestrictTo(RestrictTo.Scope.LIBRARY) |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 69 | @TargetApi(23) |
Mady Mellor | 5b2c0ce | 2017-12-08 12:16:37 -0800 | [diff] [blame] | 70 | public class RowView extends FrameLayout implements SliceView.SliceModeView, |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 71 | LargeSliceAdapter.SliceListView, View.OnClickListener { |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 72 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 73 | private static final String TAG = "RowView"; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 74 | |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 75 | // The number of items that fit on the right hand side of a small slice |
| 76 | private static final int MAX_END_ITEMS = 3; |
| 77 | |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 78 | private int mIconSize; |
| 79 | private int mPadding; |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 80 | private boolean mInSmallMode; |
| 81 | private boolean mIsHeader; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 82 | |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 83 | private LinearLayout mStartContainer; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 84 | private LinearLayout mContent; |
| 85 | private TextView mPrimaryText; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 86 | private TextView mSecondaryText; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 87 | private View mDivider; |
| 88 | private CompoundButton mToggle; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 89 | private LinearLayout mEndContainer; |
| 90 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 91 | private SliceItem mColorItem; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 92 | private SliceItem mRowAction; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 93 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 94 | public RowView(Context context) { |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 95 | super(context); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 96 | mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_icon_size); |
| 97 | mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_padding); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 98 | inflate(context, R.layout.abc_slice_small_template, this); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 99 | |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 100 | mStartContainer = (LinearLayout) findViewById(R.id.icon_frame); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 101 | mContent = (LinearLayout) findViewById(android.R.id.content); |
| 102 | mPrimaryText = (TextView) findViewById(android.R.id.title); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 103 | mSecondaryText = (TextView) findViewById(android.R.id.summary); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 104 | mDivider = findViewById(R.id.divider); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 105 | mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame); |
| 106 | } |
| 107 | |
| 108 | @Override |
Mady Mellor | 5b2c0ce | 2017-12-08 12:16:37 -0800 | [diff] [blame] | 109 | public View getView() { |
| 110 | return this; |
| 111 | } |
| 112 | |
| 113 | @Override |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 114 | public @SliceView.SliceMode int getMode() { |
| 115 | return SliceView.MODE_SMALL; |
| 116 | } |
| 117 | |
| 118 | @Override |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 119 | public void setColor(SliceItem color) { |
| 120 | mColorItem = color; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 121 | } |
| 122 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 123 | /** |
| 124 | * This is called when RowView is being used as a component in a large template. |
| 125 | */ |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 126 | @Override |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 127 | public void setSliceItem(SliceItem slice, boolean isHeader) { |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 128 | mIsHeader = isHeader; |
| 129 | mInSmallMode = false; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 130 | populateViews(slice, slice); |
| 131 | } |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 132 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 133 | /** |
| 134 | * This is called when RowView is being used as a small template. |
| 135 | */ |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 136 | @Override |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 137 | public void setSlice(Slice slice) { |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 138 | mInSmallMode = true; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 139 | Slice.Builder sb = new Slice.Builder(slice.getUri()); |
| 140 | sb.addSubSlice(slice); |
| 141 | Slice parentSlice = sb.build(); |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 142 | populateViews(parentSlice.getItems().get(0), getSummaryItem(slice)); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 143 | } |
| 144 | |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 145 | private SliceItem getSummaryItem(Slice slice) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 146 | List<SliceItem> items = slice.getItems(); |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 147 | // See if a summary is specified |
| 148 | SliceItem summary = SliceQuery.find(slice, FORMAT_SLICE, HINT_SUMMARY, null); |
| 149 | if (summary != null) { |
| 150 | return summary; |
| 151 | } |
| 152 | // First fallback is using a header |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 153 | SliceItem header = SliceQuery.find(slice, FORMAT_SLICE, null, HINT_LIST_ITEM); |
| 154 | if (header != null) { |
| 155 | return header; |
| 156 | } |
| 157 | // Otherwise use the first non-color item and use it if it's a slice |
| 158 | SliceItem firstSlice = null; |
| 159 | for (int i = 0; i < items.size(); i++) { |
| 160 | if (!FORMAT_COLOR.equals(items.get(i).getFormat())) { |
| 161 | firstSlice = items.get(i); |
| 162 | break; |
| 163 | } |
| 164 | } |
| 165 | if (firstSlice != null && FORMAT_SLICE.equals(firstSlice.getFormat())) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 166 | // Check if this slice is appropriate to use to populate small template |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 167 | if (firstSlice.hasHint(HINT_LIST)) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 168 | // Check for header, use that if it exists |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 169 | SliceItem listHeader = SliceQuery.find(firstSlice, FORMAT_SLICE, |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 170 | null, |
| 171 | new String[] { |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 172 | HINT_LIST_ITEM, HINT_LIST |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 173 | }); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 174 | if (listHeader != null) { |
| 175 | return SliceQuery.findFirstSlice(listHeader); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 176 | } else { |
| 177 | // Otherwise use the first list item |
| 178 | SliceItem newFirst = firstSlice.getSlice().getItems().get(0); |
| 179 | return SliceQuery.findFirstSlice(newFirst); |
| 180 | } |
| 181 | } else { |
| 182 | // Not a list, find first slice with non-slice children |
| 183 | return SliceQuery.findFirstSlice(firstSlice); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 184 | } |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 185 | } |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 186 | // Fallback, just use this and convert to SliceItem type slice |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 187 | Slice.Builder sb = new Slice.Builder(slice.getUri()); |
| 188 | Slice s = sb.addSubSlice(slice).build(); |
| 189 | return s.getItems().get(0); |
| 190 | } |
| 191 | |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 192 | @TargetApi(24) |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 193 | private void populateViews(SliceItem fullSlice, SliceItem sliceItem) { |
| 194 | resetViews(); |
| 195 | ArrayList<SliceItem> items = new ArrayList<>(); |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 196 | if (FORMAT_SLICE.equals(sliceItem.getFormat())) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 197 | items = new ArrayList<>(sliceItem.getSlice().getItems()); |
| 198 | } else { |
| 199 | items.add(sliceItem); |
| 200 | } |
| 201 | |
| 202 | // These are the things that can go in our small template |
| 203 | SliceItem startItem = null; |
| 204 | SliceItem titleItem = null; |
| 205 | SliceItem subTitle = null; |
| 206 | ArrayList<SliceItem> endItems = new ArrayList<>(); |
| 207 | |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 208 | // If the first item is an action check if it should be used to populate the content |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 209 | // or if it should be in the start position. |
| 210 | SliceItem firstSlice = items.size() > 0 ? items.get(0) : null; |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 211 | if (firstSlice != null && FORMAT_ACTION.equals(firstSlice.getFormat())) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 212 | if (!SliceQuery.isSimpleAction(firstSlice)) { |
| 213 | mRowAction = firstSlice; |
| 214 | items.remove(0); |
| 215 | // Populating with first action, bias to use slice associated with this action |
| 216 | items.addAll(0, mRowAction.getSlice().getItems()); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 217 | } |
| 218 | } |
| 219 | |
| 220 | // Look through our items and try to figure out main content |
| 221 | for (int i = 0; i < items.size(); i++) { |
| 222 | SliceItem item = items.get(i); |
| 223 | List<String> hints = item.getHints(); |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 224 | String itemType = item.getFormat(); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 225 | if (i == 0 && SliceQuery.isStartType((item))) { |
| 226 | startItem = item; |
| 227 | } else if (hints.contains(HINT_TITLE)) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 228 | // Things with these hints could go in the title / start position |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 229 | if ((startItem == null || !startItem.hasHint(HINT_TITLE)) |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 230 | && SliceQuery.isStartType(item)) { |
| 231 | startItem = item; |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 232 | } else if ((titleItem == null || !titleItem.hasHint(HINT_TITLE)) |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 233 | && FORMAT_TEXT.equals(itemType)) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 234 | titleItem = item; |
| 235 | } else { |
| 236 | endItems.add(item); |
| 237 | } |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 238 | } else if (FORMAT_TEXT.equals(item.getFormat())) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 239 | if (titleItem == null) { |
| 240 | titleItem = item; |
| 241 | } else if (subTitle == null) { |
| 242 | subTitle = item; |
| 243 | } else { |
| 244 | endItems.add(item); |
| 245 | } |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 246 | } else if (FORMAT_SLICE.equals(item.getFormat())) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 247 | List<SliceItem> subItems = item.getSlice().getItems(); |
| 248 | for (int j = 0; j < subItems.size(); j++) { |
| 249 | endItems.add(subItems.get(j)); |
| 250 | } |
| 251 | } else { |
| 252 | endItems.add(item); |
| 253 | } |
| 254 | } |
| 255 | |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 256 | SliceItem colorItem = SliceQuery.find(fullSlice, FORMAT_COLOR); |
| 257 | int color = colorItem != null |
| 258 | ? colorItem.getColor() |
| 259 | : (mColorItem != null) |
| 260 | ? mColorItem.getColor() |
| 261 | : -1; |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 262 | // Populate main part of the template |
| 263 | if (startItem != null) { |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 264 | if (!mIsHeader) { |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 265 | startItem = addItem(startItem, color, mStartContainer, 0 /* padding */) |
| 266 | ? startItem |
| 267 | : null; |
| 268 | if (startItem != null) { |
| 269 | endItems.remove(startItem); |
| 270 | } |
| 271 | } else { |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 272 | endItems.add(0, startItem); |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 273 | startItem = null; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 274 | } |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 275 | } |
Mady Mellor | d2fe91d | 2017-11-29 11:54:13 -0800 | [diff] [blame] | 276 | mStartContainer.setVisibility(startItem != null ? View.VISIBLE : View.GONE); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 277 | if (titleItem != null) { |
| 278 | mPrimaryText.setText(titleItem.getText()); |
| 279 | } |
| 280 | mPrimaryText.setVisibility(titleItem != null ? View.VISIBLE : View.GONE); |
| 281 | if (subTitle != null) { |
| 282 | mSecondaryText.setText(subTitle.getText()); |
| 283 | } |
| 284 | mSecondaryText.setVisibility(subTitle != null ? View.VISIBLE : View.GONE); |
| 285 | |
| 286 | // Figure out what end items we're showing |
| 287 | // If we're showing an action in this row check if it's a toggle |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 288 | if (mRowAction != null && SliceQuery.hasHints(mRowAction.getSlice(), |
| 289 | SliceHints.SUBTYPE_TOGGLE) && addToggle(mRowAction, color)) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 290 | // Can't show more end actions if we have a toggle so we're done |
| 291 | makeClickable(this); |
| 292 | return; |
| 293 | } |
| 294 | // Check if we have a toggle somewhere in our end items |
| 295 | SliceItem toggleItem = endItems.stream() |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 296 | .filter(new Predicate<SliceItem>() { |
| 297 | @Override |
| 298 | public boolean test(SliceItem item) { |
| 299 | return FORMAT_ACTION.equals(item.getFormat()) |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 300 | && SliceQuery.hasHints(item.getSlice(), SliceHints.SUBTYPE_TOGGLE); |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 301 | } |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 302 | }).findFirst().orElse(null); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 303 | if (toggleItem != null) { |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 304 | if (addToggle(toggleItem, color)) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 305 | mDivider.setVisibility(mRowAction != null ? View.VISIBLE : View.GONE); |
| 306 | makeClickable(mRowAction != null ? mContent : this); |
| 307 | // Can't show more end actions if we have a toggle so we're done |
| 308 | return; |
| 309 | } |
| 310 | } |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 311 | boolean clickableEndItem = false; |
| 312 | int itemCount = 0; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 313 | for (int i = 0; i < endItems.size(); i++) { |
| 314 | SliceItem item = endItems.get(i); |
Mady Mellor | 71ef84d | 2017-12-11 13:33:36 -0800 | [diff] [blame^] | 315 | // Only show one type of format at the end of the slice, use whatever is first |
| 316 | if (itemCount <= MAX_END_ITEMS |
| 317 | && item.getFormat().equals(endItems.get(0).getFormat())) { |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 318 | if (FORMAT_ACTION.equals(item.getFormat()) |
| 319 | && itemCount == 0 |
| 320 | && SliceQuery.hasHints(item.getSlice(), SliceHints.SUBTYPE_TOGGLE) |
| 321 | && addToggle(item, color)) { |
| 322 | // If a toggle is added we're done |
| 323 | break; |
| 324 | } else if (addItem(item, color, mEndContainer, mPadding)) { |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 325 | itemCount++; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 326 | } |
| 327 | } |
| 328 | } |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 329 | if (mRowAction != null) { |
| 330 | makeClickable(clickableEndItem ? mContent : this); |
| 331 | } |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 332 | } |
| 333 | |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 334 | /** |
| 335 | * @return Whether a toggle was added. |
| 336 | */ |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 337 | private boolean addToggle(final SliceItem toggleItem, int color) { |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 338 | if (!FORMAT_ACTION.equals(toggleItem.getFormat()) |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 339 | || !SliceQuery.hasHints(toggleItem.getSlice(), SliceHints.SUBTYPE_TOGGLE)) { |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 340 | return false; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 341 | } |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 342 | |
| 343 | // Check if this is a custom toggle |
| 344 | Icon checkedIcon = null; |
| 345 | List<SliceItem> sliceItems = toggleItem.getSlice().getItems(); |
| 346 | if (sliceItems.size() > 0) { |
| 347 | checkedIcon = FORMAT_IMAGE.equals(sliceItems.get(0).getFormat()) |
| 348 | ? sliceItems.get(0).getIcon() |
| 349 | : null; |
| 350 | } |
| 351 | if (checkedIcon != null) { |
| 352 | if (color != -1) { |
| 353 | // TODO - Should these be tinted? What if the app wants diff colors per state? |
| 354 | checkedIcon.setTint(color); |
| 355 | } |
| 356 | mToggle = new ToggleButton(getContext()); |
| 357 | ((ToggleButton) mToggle).setTextOff(""); |
| 358 | ((ToggleButton) mToggle).setTextOn(""); |
| 359 | mToggle.setBackground(checkedIcon.loadDrawable(getContext())); |
| 360 | mEndContainer.addView(mToggle); |
| 361 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mToggle.getLayoutParams(); |
| 362 | lp.width = mIconSize; |
| 363 | lp.height = mIconSize; |
| 364 | } else { |
| 365 | mToggle = new Switch(getContext()); |
| 366 | mEndContainer.addView(mToggle); |
| 367 | } |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 368 | mToggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), HINT_SELECTED)); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 369 | mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 370 | @Override |
| 371 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { |
| 372 | try { |
| 373 | PendingIntent pi = toggleItem.getAction(); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 374 | Intent i = new Intent().putExtra(EXTRA_TOGGLE_STATE, isChecked); |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 375 | pi.send(getContext(), 0, i, null, null); |
| 376 | } catch (CanceledException e) { |
| 377 | mToggle.setSelected(!isChecked); |
| 378 | } |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 379 | } |
| 380 | }); |
| 381 | return true; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 382 | } |
| 383 | |
| 384 | /** |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 385 | * Adds simple items to a container. Simple items include actions with icons, images, or |
| 386 | * timestamps. |
| 387 | * |
| 388 | * @return Whether an item was added to the view. |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 389 | */ |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 390 | private boolean addItem(SliceItem sliceItem, int color, LinearLayout container, int padding) { |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 391 | SliceItem image = null; |
| 392 | SliceItem action = null; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 393 | SliceItem timeStamp = null; |
| 394 | if (FORMAT_ACTION.equals(sliceItem.getFormat()) |
| 395 | && !sliceItem.hasHint(SliceHints.SUBTYPE_TOGGLE)) { |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 396 | image = SliceQuery.find(sliceItem.getSlice(), FORMAT_IMAGE); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 397 | timeStamp = SliceQuery.find(sliceItem.getSlice(), FORMAT_TIMESTAMP); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 398 | action = sliceItem; |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 399 | } else if (FORMAT_IMAGE.equals(sliceItem.getFormat())) { |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 400 | image = sliceItem; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 401 | } else if (FORMAT_TIMESTAMP.equals(sliceItem.getFormat())) { |
| 402 | timeStamp = sliceItem; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 403 | } |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 404 | View addedView = null; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 405 | if (image != null) { |
| 406 | ImageView iv = new ImageView(getContext()); |
| 407 | iv.setImageIcon(image.getIcon()); |
Jason Monk | dcb5e2f | 2017-11-15 20:19:43 -0500 | [diff] [blame] | 408 | if (color != -1 && !sliceItem.hasHint(HINT_NO_TINT)) { |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 409 | iv.setColorFilter(color); |
| 410 | } |
| 411 | container.addView(iv); |
| 412 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams(); |
| 413 | lp.width = mIconSize; |
| 414 | lp.height = mIconSize; |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 415 | lp.setMarginStart(padding); |
| 416 | addedView = iv; |
| 417 | } else if (timeStamp != null) { |
| 418 | TextView tv = new TextView(getContext()); |
| 419 | tv.setText(SliceViewUtil.getRelativeTimeString(sliceItem.getTimestamp())); |
| 420 | container.addView(tv); |
| 421 | addedView = tv; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 422 | } |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 423 | if (action != null && addedView != null) { |
| 424 | final SliceItem sliceAction = action; |
| 425 | addedView.setOnClickListener(new View.OnClickListener() { |
| 426 | @Override |
| 427 | public void onClick(View v) { |
| 428 | AsyncTask.execute(new Runnable() { |
| 429 | @Override |
| 430 | public void run() { |
| 431 | try { |
| 432 | sliceAction.getAction().send(); |
| 433 | } catch (CanceledException e) { |
| 434 | e.printStackTrace(); |
| 435 | } |
| 436 | } |
| 437 | }); |
| 438 | } |
| 439 | }); |
| 440 | addedView.setBackground(SliceViewUtil.getDrawable(getContext(), |
| 441 | android.R.attr.selectableItemBackground)); |
| 442 | } |
| 443 | return addedView != null; |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 444 | } |
| 445 | |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 446 | @Override |
| 447 | public void onClick(View view) { |
Jason Monk | 0c76d30 | 2017-11-21 14:02:27 -0500 | [diff] [blame] | 448 | if (mRowAction != null && FORMAT_ACTION.equals(mRowAction.getFormat())) { |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 449 | // Check for a row action |
Jason Monk | 2a7d0fc | 2017-11-15 10:10:24 -0500 | [diff] [blame] | 450 | AsyncTask.execute(new Runnable() { |
| 451 | @Override |
| 452 | public void run() { |
| 453 | try { |
| 454 | mRowAction.getAction().send(); |
| 455 | } catch (CanceledException e) { |
| 456 | Log.w(TAG, "PendingIntent for slice cannot be sent", e); |
| 457 | } |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 458 | } |
| 459 | }); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 460 | } else if (mToggle != null) { |
| 461 | // Or no row action so let's just toggle if we've got one |
| 462 | mToggle.toggle(); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 463 | } |
| 464 | } |
| 465 | |
| 466 | private void makeClickable(View layout) { |
| 467 | layout.setOnClickListener(this); |
| 468 | layout.setBackground(SliceViewUtil.getDrawable(getContext(), |
| 469 | android.R.attr.selectableItemBackground)); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | private void resetViews() { |
| 473 | mStartContainer.removeAllViews(); |
| 474 | mEndContainer.removeAllViews(); |
Mady Mellor | db8aa8c | 2017-11-08 14:26:17 -0800 | [diff] [blame] | 475 | mPrimaryText.setText(null); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 476 | mSecondaryText.setText(null); |
Mady Mellor | c133418 | 2017-11-10 15:50:35 -0800 | [diff] [blame] | 477 | mDivider.setVisibility(View.GONE); |
Jason Monk | 8a452e9 | 2017-10-31 19:21:47 -0400 | [diff] [blame] | 478 | } |
| 479 | } |