• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             // Set whether preference is enabled.
172             if (preference instanceof InfoPreference || !enabled(item)) {
173                 preference.setEnabled(false);
174             }
175             // Set whether preference is selectable
176             if (!selectable(item)) {
177                 preference.setSelectable(false);
178             }
179             // Set the key for the preference
180             CharSequence key = getKey(item);
181             if (key != null) {
182                 preference.setKey(key.toString());
183             }
184 
185             Icon icon = getIcon(data.mStartItem);
186             if (icon != null) {
187                 boolean isIconNeedToBeProcessed =
188                         SlicePreferencesUtil.isIconNeedsToBeProcessed(item);
189                 Drawable iconDrawable = icon.loadDrawable(contextThemeWrapper);
190                 if (isIconNeedToBeProcessed && isTwoPanel) {
191                     preference.setIcon(IconUtil.getCompoundIcon(contextThemeWrapper, iconDrawable));
192                 } else {
193                     preference.setIcon(iconDrawable);
194                 }
195             }
196 
197             if (data.mTitleItem != null) {
198                 preference.setTitle(getText(data.mTitleItem));
199             }
200 
201             //Set summary
202             CharSequence subtitle =
203                     data.mSubtitleItem != null ? data.mSubtitleItem.getText() : null;
204             boolean subtitleExists = !TextUtils.isEmpty(subtitle)
205                     || (data.mSubtitleItem != null && data.mSubtitleItem.hasHint(HINT_PARTIAL));
206             if (subtitleExists) {
207                 preference.setSummary(subtitle);
208             } else {
209                 if (data.mSummaryItem != null) {
210                     preference.setSummary(getText(data.mSummaryItem));
211                 }
212             }
213 
214             // Set preview info image and text
215             CharSequence infoText = getInfoText(item);
216             CharSequence infoSummary = getInfoSummary(item);
217             boolean addInfoStatus = addInfoStatus(item);
218             IconCompat infoImage = getInfoImage(item);
219             IconCompat infoTitleIcon = getInfoTitleIcon(item);
220             Bundle b = preference.getExtras();
221             String fallbackInfoContentDescription = "";
222             if (preference.getTitle() != null) {
223                 fallbackInfoContentDescription += preference.getTitle().toString();
224             }
225             if (infoImage != null) {
226                 b.putParcelable(EXTRA_PREFERENCE_INFO_IMAGE, infoImage.toIcon());
227             }
228             if (infoTitleIcon != null) {
229                 b.putParcelable(EXTRA_PREFERENCE_INFO_TITLE_ICON, infoTitleIcon.toIcon());
230             }
231             if (infoText != null) {
232                 if (preference instanceof SliceSwitchPreference && addInfoStatus) {
233                     b.putBoolean(InfoFragment.EXTRA_INFO_HAS_STATUS, true);
234                     b.putBoolean(EXTRA_PREFERENCE_INFO_STATUS,
235                             ((SliceSwitchPreference) preference).isChecked());
236                 } else {
237                     b.putBoolean(InfoFragment.EXTRA_INFO_HAS_STATUS, false);
238                 }
239                 b.putCharSequence(EXTRA_PREFERENCE_INFO_TEXT, infoText);
240                 if (preference.getTitle() != null
241                         && !preference.getTitle().equals(infoText.toString())) {
242                     fallbackInfoContentDescription +=
243                             CONTENT_DESCRIPTION_SEPARATOR + infoText.toString();
244                 }
245 
246             }
247             if (infoSummary != null) {
248                 b.putCharSequence(EXTRA_PREFERENCE_INFO_SUMMARY, infoSummary);
249                 fallbackInfoContentDescription +=
250                         CONTENT_DESCRIPTION_SEPARATOR + infoSummary;
251             }
252             String contentDescription = getInfoContentDescription(item);
253             // Respect the content description values provided by slice.
254             // If not provided, for SlicePreference, SliceSwitchPreference,
255             // CustomContentDescriptionPreference, use the fallback value.
256             // Otherwise, do not set the contentDescription for preference. Rely on the talkback
257             // framework to generate the value itself.
258             if (!TextUtils.isEmpty(contentDescription)) {
259                 if (preference instanceof HasCustomContentDescription) {
260                     ((HasCustomContentDescription) preference).setContentDescription(
261                             contentDescription);
262                 }
263             } else {
264                 if ((preference instanceof SlicePreference)
265                         || (preference instanceof SliceSwitchPreference)
266                         || (preference instanceof CustomContentDescriptionPreference)) {
267                     ((HasCustomContentDescription) preference).setContentDescription(
268                             fallbackInfoContentDescription);
269                 }
270             }
271             if (infoImage != null || infoText != null || infoSummary != null) {
272                 preference.setFragment(InfoFragment.class.getCanonicalName());
273             }
274         }
275 
276         return preference;
277     }
278 
279     static class Data {
280         SliceItem mStartItem;
281         SliceItem mTitleItem;
282         SliceItem mSubtitleItem;
283         SliceItem mSummaryItem;
284         SliceItem mTargetSliceItem;
285         SliceItem mRadioGroupItem;
286         SliceItem mIntentItem;
287         SliceItem mFollowupIntentItem;
288         SliceItem mHasEndIconItem;
289         List<SliceItem> mEndItems = new ArrayList<>();
290         List<SliceItem> mInfoItems = new ArrayList<>();
291     }
292 
extract(SliceItem sliceItem)293     static Data extract(SliceItem sliceItem) {
294         Data data = new Data();
295         List<SliceItem> possibleStartItems =
296                 SliceQuery.findAll(sliceItem, null, HINT_TITLE, null);
297         if (possibleStartItems.size() > 0) {
298             // The start item will be at position 0 if it exists
299             String format = possibleStartItems.get(0).getFormat();
300             if ((FORMAT_ACTION.equals(format)
301                     && SliceQuery.find(possibleStartItems.get(0), FORMAT_IMAGE) != null)
302                     || FORMAT_SLICE.equals(format)
303                     || FORMAT_LONG.equals(format)
304                     || FORMAT_IMAGE.equals(format)) {
305                 data.mStartItem = possibleStartItems.get(0);
306             }
307         }
308 
309         List<SliceItem> items = sliceItem.getSlice().getItems();
310         for (int i = 0; i < items.size(); i++) {
311             final SliceItem item = items.get(i);
312             String subType = item.getSubType();
313             if (subType != null) {
314                 switch (subType) {
315                     case SlicesConstants.SUBTYPE_INFO_PREFERENCE :
316                         data.mInfoItems.add(item);
317                         break;
318                     case SlicesConstants.SUBTYPE_INTENT :
319                         data.mIntentItem = item;
320                         break;
321                     case SlicesConstants.SUBTYPE_FOLLOWUP_INTENT :
322                         data.mFollowupIntentItem = item;
323                         break;
324                     case SlicesConstants.TAG_TARGET_URI :
325                         data.mTargetSliceItem = item;
326                         break;
327                     case SlicesConstants.EXTRA_HAS_END_ICON:
328                         data.mHasEndIconItem = item;
329                         break;
330                 }
331             } else if (FORMAT_TEXT.equals(item.getFormat()) && (item.getSubType() == null)) {
332                 if ((data.mTitleItem == null || !data.mTitleItem.hasHint(HINT_TITLE))
333                         && item.hasHint(HINT_TITLE) && !item.hasHint(HINT_SUMMARY)) {
334                     data.mTitleItem = item;
335                 } else if (data.mSubtitleItem == null && !item.hasHint(HINT_SUMMARY)) {
336                     data.mSubtitleItem = item;
337                 } else if (data.mSummaryItem == null && item.hasHint(HINT_SUMMARY)) {
338                     data.mSummaryItem = item;
339                 }
340             } else {
341                 data.mEndItems.add(item);
342             }
343         }
344         data.mEndItems.remove(data.mStartItem);
345         return data;
346     }
347 
getInfoList(List<SliceItem> sliceItems)348     private static List<Pair<CharSequence, CharSequence>> getInfoList(List<SliceItem> sliceItems) {
349         List<Pair<CharSequence, CharSequence>> infoList = new ArrayList<>();
350         for (SliceItem item : sliceItems) {
351             Slice itemSlice = item.getSlice();
352             if (itemSlice != null) {
353                 CharSequence title = null;
354                 CharSequence summary = null;
355                 for (SliceItem element : itemSlice.getItems()) {
356                     if (element.getHints().contains(HINT_TITLE)) {
357                         title = element.getText();
358                     } else if (element.getHints().contains(HINT_SUMMARY)) {
359                         summary = element.getText();
360                     }
361                 }
362                 infoList.add(new Pair<CharSequence, CharSequence>(title, summary));
363             }
364         }
365         return infoList;
366     }
367 
getKey(SliceItem item)368     private static CharSequence getKey(SliceItem item) {
369         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, SlicesConstants.TAG_KEY);
370         return target != null ? target.getText() : null;
371     }
372 
getRadioGroup(SliceItem item)373     private static CharSequence getRadioGroup(SliceItem item) {
374         SliceItem target = SliceQuery.findSubtype(
375                 item, FORMAT_TEXT, SlicesConstants.TAG_RADIO_GROUP);
376         return target != null ? target.getText() : null;
377     }
378 
379     /**
380      * Get the screen title item for the slice.
381      * @param sliceItems list of SliceItem extracted from slice data.
382      * @return screen title item.
383      */
getScreenTitleItem(List<SliceContent> sliceItems)384     static SliceItem getScreenTitleItem(List<SliceContent> sliceItems) {
385         for (SliceContent contentItem : sliceItems)  {
386             SliceItem item = contentItem.getSliceItem();
387             if (item.getSubType() != null
388                     && item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_SCREEN_TITLE)) {
389                 return item;
390             }
391         }
392         return null;
393     }
394 
getRedirectSlice(List<SliceContent> sliceItems)395     static SliceItem getRedirectSlice(List<SliceContent> sliceItems) {
396         for (SliceContent contentItem : sliceItems)  {
397             SliceItem item = contentItem.getSliceItem();
398             if (item.getSubType() != null
399                     && item.getSubType().equals(SlicesConstants.TYPE_REDIRECTED_SLICE_URI)) {
400                 return item;
401             }
402         }
403         return null;
404     }
405 
getFocusedPreferenceItem(List<SliceContent> sliceItems)406     static SliceItem getFocusedPreferenceItem(List<SliceContent> sliceItems) {
407         for (SliceContent contentItem : sliceItems)  {
408             SliceItem item = contentItem.getSliceItem();
409             if (item.getSubType() != null
410                     && item.getSubType().equals(SlicesConstants.TYPE_FOCUSED_PREFERENCE)) {
411                 return item;
412             }
413         }
414         return null;
415     }
416 
getEmbeddedItem(List<SliceContent> sliceItems)417     static SliceItem getEmbeddedItem(List<SliceContent> sliceItems) {
418         for (SliceContent contentItem : sliceItems)  {
419             SliceItem item = contentItem.getSliceItem();
420             if (item.getSubType() != null
421                     && item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED)) {
422                 return item;
423             }
424         }
425         return null;
426     }
427 
isIconNeedsToBeProcessed(SliceItem sliceItem)428     private static boolean isIconNeedsToBeProcessed(SliceItem sliceItem) {
429         List<SliceItem> items = sliceItem.getSlice().getItems();
430         for (SliceItem item : items)  {
431             if (item.getSubType() != null && item.getSubType().equals(
432                     SlicesConstants.SUBTYPE_ICON_NEED_TO_BE_PROCESSED)) {
433                 return item.getInt() == 1;
434             }
435         }
436         return false;
437     }
438 
getButtonStyle(SliceItem sliceItem)439     private static int getButtonStyle(SliceItem sliceItem) {
440         List<SliceItem> items = sliceItem.getSlice().getItems();
441         for (SliceItem item : items)  {
442             if (item.getSubType() != null
443                     && item.getSubType().equals(SlicesConstants.SUBTYPE_BUTTON_STYLE)) {
444                 return item.getInt();
445             }
446         }
447         return -1;
448     }
449 
getSeekbarMin(SliceItem sliceItem)450     private static int getSeekbarMin(SliceItem sliceItem) {
451         List<SliceItem> items = sliceItem.getSlice().getItems();
452         for (SliceItem item : items)  {
453             if (item.getSubType() != null
454                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_MIN)) {
455                 return item.getInt();
456             }
457         }
458         return -1;
459     }
460 
getSeekbarMax(SliceItem sliceItem)461     private static int getSeekbarMax(SliceItem sliceItem) {
462         List<SliceItem> items = sliceItem.getSlice().getItems();
463         for (SliceItem item : items)  {
464             if (item.getSubType() != null
465                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_MAX)) {
466                 return item.getInt();
467             }
468         }
469         return -1;
470     }
471 
getSeekbarValue(SliceItem sliceItem)472     private static int getSeekbarValue(SliceItem sliceItem) {
473         List<SliceItem> items = sliceItem.getSlice().getItems();
474         for (SliceItem item : items)  {
475             if (item.getSubType() != null
476                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_VALUE)) {
477                 return item.getInt();
478             }
479         }
480         return -1;
481     }
482 
enabled(SliceItem sliceItem)483     private static boolean enabled(SliceItem sliceItem) {
484         List<SliceItem> items = sliceItem.getSlice().getItems();
485         for (SliceItem item : items)  {
486             if (item.getSubType() != null
487                     && item.getSubType().equals(SlicesConstants.SUBTYPE_IS_ENABLED)) {
488                 return item.getInt() == 1;
489             }
490         }
491         return true;
492     }
493 
selectable(SliceItem sliceItem)494     private static boolean selectable(SliceItem sliceItem) {
495         List<SliceItem> items = sliceItem.getSlice().getItems();
496         for (SliceItem item : items)  {
497             if (item.getSubType() != null
498                     && item.getSubType().equals(SlicesConstants.SUBTYPE_IS_SELECTABLE)) {
499                 return item.getInt() == 1;
500             }
501         }
502         return true;
503     }
504 
addInfoStatus(SliceItem sliceItem)505     private static boolean addInfoStatus(SliceItem sliceItem) {
506         List<SliceItem> items = sliceItem.getSlice().getItems();
507         for (SliceItem item : items)  {
508             if (item.getSubType() != null
509                     && item.getSubType().equals(EXTRA_ADD_INFO_STATUS)) {
510                 return item.getInt() == 1;
511             }
512         }
513         return true;
514     }
515 
hasEndIcon(SliceItem item)516     private static boolean hasEndIcon(SliceItem item) {
517         return item != null && item.getInt() > 0;
518     }
519 
520     /**
521      * Checks if custom content description should be forced to be used if provided. This function
522      * can be extended with more cases if needed.
523      *
524      * @param item The {@link SliceItem} containing the necessary information.
525      * @return <code>true</code> if custom content description should be used.
526      */
shouldForceContentDescription(SliceItem sliceItem)527     private static boolean shouldForceContentDescription(SliceItem sliceItem) {
528         List<SliceItem> items = sliceItem.getSlice().getItems();
529         for (SliceItem item : items)  {
530             // Checks if an end icon has been set.
531             if (item.getSubType() != null
532                     && item.getSubType().equals(SlicesConstants.EXTRA_HAS_END_ICON)) {
533                 return hasEndIcon(item);
534             }
535         }
536         return false;
537     }
538 
539     /**
540      * Get the text from the SliceItem.
541      */
getText(SliceItem item)542     static CharSequence getText(SliceItem item) {
543         if (item == null) {
544             return null;
545         }
546         return item.getText();
547     }
548 
549     /** Get the icon from the SliceItem if available */
getIcon(SliceItem startItem)550     static Icon getIcon(SliceItem startItem) {
551         if (startItem != null && startItem.getSlice() != null
552                 && startItem.getSlice().getItems() != null
553                 && startItem.getSlice().getItems().size() > 0) {
554             SliceItem iconItem = startItem.getSlice().getItems().get(0);
555             if (FORMAT_IMAGE.equals(iconItem.getFormat())) {
556                 IconCompat icon = iconItem.getIcon();
557                 return icon.toIcon();
558             }
559         }
560         return null;
561     }
562 
getStatusPath(String uriString)563     static Uri getStatusPath(String uriString) {
564         Uri statusUri = Uri.parse(uriString)
565                 .buildUpon().path("/" + SlicesConstants.PATH_STATUS).build();
566         return statusUri;
567     }
568 
getPageId(SliceItem item)569     static int getPageId(SliceItem item) {
570         SliceItem target = SliceQuery.findSubtype(item, FORMAT_INT, EXTRA_PAGE_ID);
571         return target != null ? target.getInt() : 0;
572     }
573 
getActionId(SliceItem item)574     private static int getActionId(SliceItem item) {
575         SliceItem target = SliceQuery.findSubtype(item, FORMAT_INT, EXTRA_ACTION_ID);
576         return target != null ? target.getInt() : 0;
577     }
578 
579 
getInfoText(SliceItem item)580     private static CharSequence getInfoText(SliceItem item) {
581         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, EXTRA_PREFERENCE_INFO_TEXT);
582         return target != null ? target.getText() : null;
583     }
584 
getInfoSummary(SliceItem item)585     private static CharSequence getInfoSummary(SliceItem item) {
586         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, EXTRA_PREFERENCE_INFO_SUMMARY);
587         return target != null ? target.getText() : null;
588     }
589 
getInfoImage(SliceItem item)590     private static IconCompat getInfoImage(SliceItem item) {
591         SliceItem target = SliceQuery.findSubtype(item, FORMAT_IMAGE, EXTRA_PREFERENCE_INFO_IMAGE);
592         return target != null ? target.getIcon() : null;
593     }
594 
getInfoTitleIcon(SliceItem item)595     private static IconCompat getInfoTitleIcon(SliceItem item) {
596         SliceItem target = SliceQuery.findSubtype(
597                 item, FORMAT_IMAGE, EXTRA_PREFERENCE_INFO_TITLE_ICON);
598         return target != null ? target.getIcon() : null;
599     }
600 
601     /**
602      * Get the content description from SliceItem if available
603      */
getInfoContentDescription( SliceItem sliceItem)604     private static String getInfoContentDescription(
605             SliceItem sliceItem) {
606         List<SliceItem> items = sliceItem.getSlice().getItems();
607         for (SliceItem item : items)  {
608             if (item.getSubType() != null
609                     && item.getSubType().equals(SUBTYPE_CONTENT_DESCRIPTION)) {
610                 return item.getText().toString();
611             }
612         }
613         return null;
614     }
615 }
616