1 /*
2  * Copyright (C) 2019 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;
18 
19 import static android.app.slice.Slice.HINT_PARTIAL;
20 import static android.app.slice.Slice.HINT_SUMMARY;
21 import static android.app.slice.Slice.HINT_TITLE;
22 import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION;
23 import static android.app.slice.SliceItem.FORMAT_ACTION;
24 import static android.app.slice.SliceItem.FORMAT_IMAGE;
25 import static android.app.slice.SliceItem.FORMAT_INT;
26 import static android.app.slice.SliceItem.FORMAT_LONG;
27 import static android.app.slice.SliceItem.FORMAT_SLICE;
28 import static android.app.slice.SliceItem.FORMAT_TEXT;
29 
30 import static com.android.tv.twopanelsettings.slices.HasCustomContentDescription.CONTENT_DESCRIPTION_SEPARATOR;
31 import static com.android.tv.twopanelsettings.slices.SlicesConstants.CHECKMARK;
32 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_ACTION_ID;
33 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_ADD_INFO_STATUS;
34 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PAGE_ID;
35 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_IMAGE;
36 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_STATUS;
37 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_SUMMARY;
38 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_TEXT;
39 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_TITLE_ICON;
40 import static com.android.tv.twopanelsettings.slices.SlicesConstants.RADIO;
41 import static com.android.tv.twopanelsettings.slices.SlicesConstants.SEEKBAR;
42 import static com.android.tv.twopanelsettings.slices.SlicesConstants.SWITCH;
43 
44 import android.graphics.drawable.Drawable;
45 import android.graphics.drawable.Icon;
46 import android.net.Uri;
47 import android.os.Bundle;
48 import android.text.TextUtils;
49 import android.util.Pair;
50 import android.view.ContextThemeWrapper;
51 
52 import androidx.core.graphics.drawable.IconCompat;
53 import androidx.preference.Preference;
54 import androidx.slice.Slice;
55 import androidx.slice.SliceItem;
56 import androidx.slice.core.SliceActionImpl;
57 import androidx.slice.core.SliceQuery;
58 import androidx.slice.widget.SliceContent;
59 
60 import com.android.tv.twopanelsettings.IconUtil;
61 import com.android.tv.twopanelsettings.R;
62 
63 import java.util.ArrayList;
64 import java.util.List;
65 
66 /**
67  * Generate corresponding preference based upon the slice data.
68  */
69 public final class SlicePreferencesUtil {
70 
getPreference(SliceItem item, ContextThemeWrapper contextThemeWrapper, String className, boolean isTwoPanel)71     static Preference getPreference(SliceItem item, ContextThemeWrapper contextThemeWrapper,
72             String className, boolean isTwoPanel) {
73         Preference preference = null;
74         if (item == null) {
75             return null;
76         }
77         Data data = extract(item);
78         if (item.getSubType() != null) {
79             String subType = item.getSubType();
80             if (subType.equals(SlicesConstants.TYPE_PREFERENCE)
81                     || subType.equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED)
82                     || subType.equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED_PLACEHOLDER)) {
83                 // TODO: Figure out all the possible cases and reorganize the logic
84                 if (data.mInfoItems.size() > 0) {
85                     preference = new InfoPreference(
86                                 contextThemeWrapper, getInfoList(data.mInfoItems));
87                 } else if (data.mIntentItem != null) {
88                     SliceActionImpl action = new SliceActionImpl(data.mIntentItem);
89                     if (action != null) {
90                         // Currently if we don't set icon for the SliceAction, slice lib will
91                         // automatically treat it as a toggle. To distinguish preference action and
92                         // toggle action, we need to add a subtype if this is a preference action.
93                         preference = new SlicePreference(contextThemeWrapper);
94                         ((SlicePreference) preference).setSliceAction(action);
95                         ((SlicePreference) preference).setActionId(getActionId(item));
96                         if (data.mFollowupIntentItem != null) {
97                             SliceActionImpl followUpAction =
98                                     new SliceActionImpl(data.mFollowupIntentItem);
99                             ((SlicePreference) preference).setFollowupSliceAction(followUpAction);
100                         }
101                     }
102                 } else if (data.mEndItems.size() > 0 && data.mEndItems.get(0) != null) {
103                     SliceActionImpl action = new SliceActionImpl(data.mEndItems.get(0));
104                     if (action != null) {
105                         int buttonStyle = SlicePreferencesUtil.getButtonStyle(item);
106                         switch (buttonStyle) {
107                             case CHECKMARK :
108                                 preference = new SliceCheckboxPreference(
109                                         contextThemeWrapper, action);
110                                 break;
111                             case SWITCH :
112                                 preference = new SliceSwitchPreference(contextThemeWrapper, action);
113                                 break;
114                             case RADIO:
115                                 preference = new SliceRadioPreference(contextThemeWrapper, action);
116                                 preference.setLayoutResource(R.layout.preference_reversed_widget);
117                                 if (getRadioGroup(item) != null) {
118                                     ((SliceRadioPreference) preference).setRadioGroup(
119                                             getRadioGroup(item).toString());
120                                 }
121                                 break;
122                             case SEEKBAR :
123                                 int min = SlicePreferencesUtil.getSeekbarMin(item);
124                                 int max = SlicePreferencesUtil.getSeekbarMax(item);
125                                 int value = SlicePreferencesUtil.getSeekbarValue(item);
126                                 preference = new SliceSeekbarPreference(
127                                         contextThemeWrapper, action, min, max, value);
128                                 break;
129                         }
130                         if (preference instanceof HasSliceAction) {
131                             ((HasSliceAction) preference).setActionId(getActionId(item));
132                         }
133                         if (data.mFollowupIntentItem != null) {
134                             SliceActionImpl followUpAction =
135                                     new SliceActionImpl(data.mFollowupIntentItem);
136                             ((HasSliceAction) preference).setFollowupSliceAction(followUpAction);
137 
138                         }
139                     }
140                 }
141 
142                 CharSequence uri = getText(data.mTargetSliceItem);
143                 if (uri == null || TextUtils.isEmpty(uri)) {
144                     if (preference == null) {
145                         preference = new CustomContentDescriptionPreference(contextThemeWrapper);
146                     }
147                 } else {
148                     if (preference == null) {
149                         if (subType.equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED_PLACEHOLDER)) {
150                             preference = new EmbeddedSlicePreference(contextThemeWrapper,
151                                     String.valueOf(uri));
152                         } else {
153                             preference = new SlicePreference(contextThemeWrapper);
154                         }
155                         if (hasEndIcon(data.mHasEndIconItem)) {
156                             preference.setLayoutResource(R.layout.preference_reversed_icon);
157                         }
158                     }
159                     ((HasSliceUri) preference).setUri(uri.toString());
160                     if (preference instanceof HasSliceAction) {
161                         ((HasSliceAction) preference).setActionId(getActionId(item));
162                     }
163                     preference.setFragment(className);
164                 }
165             } else if (item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_CATEGORY)) {
166                 preference = new CustomContentDescriptionPreferenceCategory(contextThemeWrapper);
167             }
168         }
169 
170         if (preference != null) {
171             boolean isEnabled = enabled(item);
172             // Set whether preference is enabled.
173             if (preference instanceof InfoPreference || !isEnabled) {
174                 preference.setEnabled(false);
175             }
176             // Set whether preference is selectable
177             if (!selectable(item) || !isEnabled) {
178                 preference.setSelectable(false);
179             }
180             // Set the key for the preference
181             CharSequence key = getKey(item);
182             if (key != null) {
183                 preference.setKey(key.toString());
184             }
185 
186             Icon icon = getIcon(data.mStartItem);
187             if (icon != null) {
188                 boolean isIconNeedToBeProcessed =
189                         SlicePreferencesUtil.isIconNeedsToBeProcessed(item);
190                 Drawable iconDrawable = icon.loadDrawable(contextThemeWrapper);
191                 if (isIconNeedToBeProcessed && isTwoPanel) {
192                     preference.setIcon(IconUtil.getCompoundIcon(contextThemeWrapper, iconDrawable));
193                 } else {
194                     preference.setIcon(iconDrawable);
195                 }
196             }
197 
198             if (data.mTitleItem != null) {
199                 preference.setTitle(getText(data.mTitleItem));
200             }
201 
202             //Set summary
203             CharSequence subtitle =
204                     data.mSubtitleItem != null ? data.mSubtitleItem.getText() : null;
205             boolean subtitleExists = !TextUtils.isEmpty(subtitle)
206                     || (data.mSubtitleItem != null && data.mSubtitleItem.hasHint(HINT_PARTIAL));
207             if (subtitleExists) {
208                 preference.setSummary(subtitle);
209             } else {
210                 if (data.mSummaryItem != null) {
211                     preference.setSummary(getText(data.mSummaryItem));
212                 }
213             }
214 
215             // Set preview info image and text
216             CharSequence infoText = getInfoText(item);
217             CharSequence infoSummary = getInfoSummary(item);
218             boolean addInfoStatus = addInfoStatus(item);
219             IconCompat infoImage = getInfoImage(item);
220             IconCompat infoTitleIcon = getInfoTitleIcon(item);
221             Bundle b = preference.getExtras();
222             String fallbackInfoContentDescription = "";
223             if (preference.getTitle() != null) {
224                 fallbackInfoContentDescription += preference.getTitle().toString();
225             }
226             if (infoImage != null) {
227                 b.putParcelable(EXTRA_PREFERENCE_INFO_IMAGE, infoImage.toIcon());
228             }
229             if (infoTitleIcon != null) {
230                 b.putParcelable(EXTRA_PREFERENCE_INFO_TITLE_ICON, infoTitleIcon.toIcon());
231             }
232             if (infoText != null) {
233                 if (preference instanceof SliceSwitchPreference && addInfoStatus) {
234                     b.putBoolean(InfoFragment.EXTRA_INFO_HAS_STATUS, true);
235                     b.putBoolean(EXTRA_PREFERENCE_INFO_STATUS,
236                             ((SliceSwitchPreference) preference).isChecked());
237                 } else {
238                     b.putBoolean(InfoFragment.EXTRA_INFO_HAS_STATUS, false);
239                 }
240                 b.putCharSequence(EXTRA_PREFERENCE_INFO_TEXT, infoText);
241                 if (preference.getTitle() != null
242                         && !preference.getTitle().equals(infoText.toString())) {
243                     fallbackInfoContentDescription +=
244                             CONTENT_DESCRIPTION_SEPARATOR + infoText.toString();
245                 }
246 
247             }
248             if (infoSummary != null) {
249                 b.putCharSequence(EXTRA_PREFERENCE_INFO_SUMMARY, infoSummary);
250                 fallbackInfoContentDescription +=
251                         CONTENT_DESCRIPTION_SEPARATOR + infoSummary;
252             }
253             String contentDescription = getInfoContentDescription(item);
254             // Respect the content description values provided by slice.
255             // If not provided, for SlicePreference, SliceSwitchPreference,
256             // CustomContentDescriptionPreference, use the fallback value.
257             // Otherwise, do not set the contentDescription for preference. Rely on the talkback
258             // framework to generate the value itself.
259             if (!TextUtils.isEmpty(contentDescription)) {
260                 if (preference instanceof HasCustomContentDescription) {
261                     ((HasCustomContentDescription) preference).setContentDescription(
262                             contentDescription);
263                 }
264             } else {
265                 if ((preference instanceof SlicePreference)
266                         || (preference instanceof SliceSwitchPreference)
267                         || (preference instanceof CustomContentDescriptionPreference)) {
268                     ((HasCustomContentDescription) preference).setContentDescription(
269                             fallbackInfoContentDescription);
270                 }
271             }
272             if (infoImage != null || infoText != null || infoSummary != null) {
273                 preference.setFragment(InfoFragment.class.getCanonicalName());
274             }
275         }
276 
277         return preference;
278     }
279 
280     static class Data {
281         SliceItem mStartItem;
282         SliceItem mTitleItem;
283         SliceItem mSubtitleItem;
284         SliceItem mSummaryItem;
285         SliceItem mTargetSliceItem;
286         SliceItem mRadioGroupItem;
287         SliceItem mIntentItem;
288         SliceItem mFollowupIntentItem;
289         SliceItem mHasEndIconItem;
290         List<SliceItem> mEndItems = new ArrayList<>();
291         List<SliceItem> mInfoItems = new ArrayList<>();
292     }
293 
extract(SliceItem sliceItem)294     static Data extract(SliceItem sliceItem) {
295         Data data = new Data();
296         List<SliceItem> possibleStartItems =
297                 SliceQuery.findAll(sliceItem, null, HINT_TITLE, null);
298         if (possibleStartItems.size() > 0) {
299             // The start item will be at position 0 if it exists
300             String format = possibleStartItems.get(0).getFormat();
301             if ((FORMAT_ACTION.equals(format)
302                     && SliceQuery.find(possibleStartItems.get(0), FORMAT_IMAGE) != null)
303                     || FORMAT_SLICE.equals(format)
304                     || FORMAT_LONG.equals(format)
305                     || FORMAT_IMAGE.equals(format)) {
306                 data.mStartItem = possibleStartItems.get(0);
307             }
308         }
309 
310         List<SliceItem> items = sliceItem.getSlice().getItems();
311         for (int i = 0; i < items.size(); i++) {
312             final SliceItem item = items.get(i);
313             String subType = item.getSubType();
314             if (subType != null) {
315                 switch (subType) {
316                     case SlicesConstants.SUBTYPE_INFO_PREFERENCE :
317                         data.mInfoItems.add(item);
318                         break;
319                     case SlicesConstants.SUBTYPE_INTENT :
320                         data.mIntentItem = item;
321                         break;
322                     case SlicesConstants.SUBTYPE_FOLLOWUP_INTENT :
323                         data.mFollowupIntentItem = item;
324                         break;
325                     case SlicesConstants.TAG_TARGET_URI :
326                         data.mTargetSliceItem = item;
327                         break;
328                     case SlicesConstants.EXTRA_HAS_END_ICON:
329                         data.mHasEndIconItem = item;
330                         break;
331                 }
332             } else if (FORMAT_TEXT.equals(item.getFormat()) && (item.getSubType() == null)) {
333                 if ((data.mTitleItem == null || !data.mTitleItem.hasHint(HINT_TITLE))
334                         && item.hasHint(HINT_TITLE) && !item.hasHint(HINT_SUMMARY)) {
335                     data.mTitleItem = item;
336                 } else if (data.mSubtitleItem == null && !item.hasHint(HINT_SUMMARY)) {
337                     data.mSubtitleItem = item;
338                 } else if (data.mSummaryItem == null && item.hasHint(HINT_SUMMARY)) {
339                     data.mSummaryItem = item;
340                 }
341             } else {
342                 data.mEndItems.add(item);
343             }
344         }
345         data.mEndItems.remove(data.mStartItem);
346         return data;
347     }
348 
getInfoList(List<SliceItem> sliceItems)349     private static List<Pair<CharSequence, CharSequence>> getInfoList(List<SliceItem> sliceItems) {
350         List<Pair<CharSequence, CharSequence>> infoList = new ArrayList<>();
351         for (SliceItem item : sliceItems) {
352             Slice itemSlice = item.getSlice();
353             if (itemSlice != null) {
354                 CharSequence title = null;
355                 CharSequence summary = null;
356                 for (SliceItem element : itemSlice.getItems()) {
357                     if (element.getHints().contains(HINT_TITLE)) {
358                         title = element.getText();
359                     } else if (element.getHints().contains(HINT_SUMMARY)) {
360                         summary = element.getText();
361                     }
362                 }
363                 infoList.add(new Pair<CharSequence, CharSequence>(title, summary));
364             }
365         }
366         return infoList;
367     }
368 
getKey(SliceItem item)369     private static CharSequence getKey(SliceItem item) {
370         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, SlicesConstants.TAG_KEY);
371         return target != null ? target.getText() : null;
372     }
373 
getRadioGroup(SliceItem item)374     private static CharSequence getRadioGroup(SliceItem item) {
375         SliceItem target = SliceQuery.findSubtype(
376                 item, FORMAT_TEXT, SlicesConstants.TAG_RADIO_GROUP);
377         return target != null ? target.getText() : null;
378     }
379 
380     /**
381      * Get the screen title item for the slice.
382      * @param sliceItems list of SliceItem extracted from slice data.
383      * @return screen title item.
384      */
getScreenTitleItem(List<SliceContent> sliceItems)385     static SliceItem getScreenTitleItem(List<SliceContent> sliceItems) {
386         for (SliceContent contentItem : sliceItems)  {
387             SliceItem item = contentItem.getSliceItem();
388             if (item.getSubType() != null
389                     && item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_SCREEN_TITLE)) {
390                 return item;
391             }
392         }
393         return null;
394     }
395 
getRedirectSlice(List<SliceContent> sliceItems)396     static SliceItem getRedirectSlice(List<SliceContent> sliceItems) {
397         for (SliceContent contentItem : sliceItems)  {
398             SliceItem item = contentItem.getSliceItem();
399             if (item.getSubType() != null
400                     && item.getSubType().equals(SlicesConstants.TYPE_REDIRECTED_SLICE_URI)) {
401                 return item;
402             }
403         }
404         return null;
405     }
406 
getFocusedPreferenceItem(List<SliceContent> sliceItems)407     static SliceItem getFocusedPreferenceItem(List<SliceContent> sliceItems) {
408         for (SliceContent contentItem : sliceItems)  {
409             SliceItem item = contentItem.getSliceItem();
410             if (item.getSubType() != null
411                     && item.getSubType().equals(SlicesConstants.TYPE_FOCUSED_PREFERENCE)) {
412                 return item;
413             }
414         }
415         return null;
416     }
417 
getEmbeddedItem(List<SliceContent> sliceItems)418     static SliceItem getEmbeddedItem(List<SliceContent> sliceItems) {
419         for (SliceContent contentItem : sliceItems)  {
420             SliceItem item = contentItem.getSliceItem();
421             if (item.getSubType() != null
422                     && item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED)) {
423                 return item;
424             }
425         }
426         return null;
427     }
428 
isIconNeedsToBeProcessed(SliceItem sliceItem)429     private static boolean isIconNeedsToBeProcessed(SliceItem sliceItem) {
430         List<SliceItem> items = sliceItem.getSlice().getItems();
431         for (SliceItem item : items)  {
432             if (item.getSubType() != null && item.getSubType().equals(
433                     SlicesConstants.SUBTYPE_ICON_NEED_TO_BE_PROCESSED)) {
434                 return item.getInt() == 1;
435             }
436         }
437         return false;
438     }
439 
getButtonStyle(SliceItem sliceItem)440     private static int getButtonStyle(SliceItem sliceItem) {
441         List<SliceItem> items = sliceItem.getSlice().getItems();
442         for (SliceItem item : items)  {
443             if (item.getSubType() != null
444                     && item.getSubType().equals(SlicesConstants.SUBTYPE_BUTTON_STYLE)) {
445                 return item.getInt();
446             }
447         }
448         return -1;
449     }
450 
getSeekbarMin(SliceItem sliceItem)451     private static int getSeekbarMin(SliceItem sliceItem) {
452         List<SliceItem> items = sliceItem.getSlice().getItems();
453         for (SliceItem item : items)  {
454             if (item.getSubType() != null
455                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_MIN)) {
456                 return item.getInt();
457             }
458         }
459         return -1;
460     }
461 
getSeekbarMax(SliceItem sliceItem)462     private static int getSeekbarMax(SliceItem sliceItem) {
463         List<SliceItem> items = sliceItem.getSlice().getItems();
464         for (SliceItem item : items)  {
465             if (item.getSubType() != null
466                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_MAX)) {
467                 return item.getInt();
468             }
469         }
470         return -1;
471     }
472 
getSeekbarValue(SliceItem sliceItem)473     private static int getSeekbarValue(SliceItem sliceItem) {
474         List<SliceItem> items = sliceItem.getSlice().getItems();
475         for (SliceItem item : items)  {
476             if (item.getSubType() != null
477                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_VALUE)) {
478                 return item.getInt();
479             }
480         }
481         return -1;
482     }
483 
enabled(SliceItem sliceItem)484     private static boolean enabled(SliceItem sliceItem) {
485         List<SliceItem> items = sliceItem.getSlice().getItems();
486         for (SliceItem item : items)  {
487             if (item.getSubType() != null
488                     && item.getSubType().equals(SlicesConstants.SUBTYPE_IS_ENABLED)) {
489                 return item.getInt() == 1;
490             }
491         }
492         return true;
493     }
494 
selectable(SliceItem sliceItem)495     private static boolean selectable(SliceItem sliceItem) {
496         List<SliceItem> items = sliceItem.getSlice().getItems();
497         for (SliceItem item : items)  {
498             if (item.getSubType() != null
499                     && item.getSubType().equals(SlicesConstants.SUBTYPE_IS_SELECTABLE)) {
500                 return item.getInt() == 1;
501             }
502         }
503         return true;
504     }
505 
addInfoStatus(SliceItem sliceItem)506     private static boolean addInfoStatus(SliceItem sliceItem) {
507         List<SliceItem> items = sliceItem.getSlice().getItems();
508         for (SliceItem item : items)  {
509             if (item.getSubType() != null
510                     && item.getSubType().equals(EXTRA_ADD_INFO_STATUS)) {
511                 return item.getInt() == 1;
512             }
513         }
514         return true;
515     }
516 
hasEndIcon(SliceItem item)517     private static boolean hasEndIcon(SliceItem item) {
518         return item != null && item.getInt() > 0;
519     }
520 
521     /**
522      * Checks if custom content description should be forced to be used if provided. This function
523      * can be extended with more cases if needed.
524      *
525      * @param item The {@link SliceItem} containing the necessary information.
526      * @return <code>true</code> if custom content description should be used.
527      */
shouldForceContentDescription(SliceItem sliceItem)528     private static boolean shouldForceContentDescription(SliceItem sliceItem) {
529         List<SliceItem> items = sliceItem.getSlice().getItems();
530         for (SliceItem item : items)  {
531             // Checks if an end icon has been set.
532             if (item.getSubType() != null
533                     && item.getSubType().equals(SlicesConstants.EXTRA_HAS_END_ICON)) {
534                 return hasEndIcon(item);
535             }
536         }
537         return false;
538     }
539 
540     /**
541      * Get the text from the SliceItem.
542      */
getText(SliceItem item)543     static CharSequence getText(SliceItem item) {
544         if (item == null) {
545             return null;
546         }
547         return item.getText();
548     }
549 
550     /** Get the icon from the SliceItem if available */
getIcon(SliceItem startItem)551     static Icon getIcon(SliceItem startItem) {
552         if (startItem != null && startItem.getSlice() != null
553                 && startItem.getSlice().getItems() != null
554                 && startItem.getSlice().getItems().size() > 0) {
555             SliceItem iconItem = startItem.getSlice().getItems().get(0);
556             if (FORMAT_IMAGE.equals(iconItem.getFormat())) {
557                 IconCompat icon = iconItem.getIcon();
558                 return icon.toIcon();
559             }
560         }
561         return null;
562     }
563 
getStatusPath(String uriString)564     static Uri getStatusPath(String uriString) {
565         Uri statusUri = Uri.parse(uriString)
566                 .buildUpon().path("/" + SlicesConstants.PATH_STATUS).build();
567         return statusUri;
568     }
569 
getPageId(SliceItem item)570     static int getPageId(SliceItem item) {
571         SliceItem target = SliceQuery.findSubtype(item, FORMAT_INT, EXTRA_PAGE_ID);
572         return target != null ? target.getInt() : 0;
573     }
574 
getActionId(SliceItem item)575     private static int getActionId(SliceItem item) {
576         SliceItem target = SliceQuery.findSubtype(item, FORMAT_INT, EXTRA_ACTION_ID);
577         return target != null ? target.getInt() : 0;
578     }
579 
580 
getInfoText(SliceItem item)581     private static CharSequence getInfoText(SliceItem item) {
582         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, EXTRA_PREFERENCE_INFO_TEXT);
583         return target != null ? target.getText() : null;
584     }
585 
getInfoSummary(SliceItem item)586     private static CharSequence getInfoSummary(SliceItem item) {
587         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, EXTRA_PREFERENCE_INFO_SUMMARY);
588         return target != null ? target.getText() : null;
589     }
590 
getInfoImage(SliceItem item)591     private static IconCompat getInfoImage(SliceItem item) {
592         SliceItem target = SliceQuery.findSubtype(item, FORMAT_IMAGE, EXTRA_PREFERENCE_INFO_IMAGE);
593         return target != null ? target.getIcon() : null;
594     }
595 
getInfoTitleIcon(SliceItem item)596     private static IconCompat getInfoTitleIcon(SliceItem item) {
597         SliceItem target = SliceQuery.findSubtype(
598                 item, FORMAT_IMAGE, EXTRA_PREFERENCE_INFO_TITLE_ICON);
599         return target != null ? target.getIcon() : null;
600     }
601 
602     /**
603      * Get the content description from SliceItem if available
604      */
getInfoContentDescription( SliceItem sliceItem)605     private static String getInfoContentDescription(
606             SliceItem sliceItem) {
607         List<SliceItem> items = sliceItem.getSlice().getItems();
608         for (SliceItem item : items)  {
609             if (item.getSubType() != null
610                     && item.getSubType().equals(SUBTYPE_CONTENT_DESCRIPTION)) {
611                 return item.getText().toString();
612             }
613         }
614         return null;
615     }
616 }
617