• 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.SWITCH;
42 import static com.android.tv.twopanelsettings.slices.SlicesConstants.SEEKBAR;
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.preference.PreferenceCategory;
55 import androidx.slice.Slice;
56 import androidx.slice.SliceItem;
57 import androidx.slice.core.SliceActionImpl;
58 import androidx.slice.core.SliceQuery;
59 import androidx.slice.widget.SliceContent;
60 
61 import com.android.tv.twopanelsettings.IconUtil;
62 import com.android.tv.twopanelsettings.R;
63 
64 import java.util.ArrayList;
65 import java.util.List;
66 
67 /**
68  * Generate corresponding preference based upon the slice data.
69  */
70 public final class SlicePreferencesUtil {
71 
getPreference(SliceItem item, ContextThemeWrapper contextThemeWrapper, String className, boolean isTwoPanel)72     static Preference getPreference(SliceItem item, ContextThemeWrapper contextThemeWrapper,
73             String className, boolean isTwoPanel) {
74         Preference preference = null;
75         if (item == null) {
76             return null;
77         }
78         Data data = extract(item);
79         if (item.getSubType() != null) {
80             String subType = item.getSubType();
81             if (subType.equals(SlicesConstants.TYPE_PREFERENCE)
82                     || subType.equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED)) {
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                         // if contains info text
146                         if (getInfoSummary(item) != null) {
147                             preference = new CustomContentDescriptionPreference(
148                                         contextThemeWrapper);
149                         } else {
150                             preference = new Preference(contextThemeWrapper);
151                         }
152                     }
153                 } else {
154                     if (preference == null) {
155                         preference = new SlicePreference(contextThemeWrapper);
156                     }
157                     ((HasSliceUri) preference).setUri(uri.toString());
158                     if (preference instanceof HasSliceAction) {
159                         ((HasSliceAction) preference).setActionId(getActionId(item));
160                     }
161                     preference.setFragment(className);
162                 }
163             } else if (item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_CATEGORY)) {
164                 preference = new PreferenceCategory(contextThemeWrapper);
165             }
166         }
167 
168         if (preference != null) {
169             // Set whether preference is enabled.
170             if (preference instanceof InfoPreference || !enabled(item)) {
171                 preference.setEnabled(false);
172             }
173             // Set whether preference is selectable
174             if (!selectable(item)) {
175                 preference.setSelectable(false);
176             }
177             // Set the key for the preference
178             CharSequence key = getKey(item);
179             if (key != null) {
180                 preference.setKey(key.toString());
181             }
182 
183             Icon icon = getIcon(data.mStartItem);
184             if (icon != null) {
185                 boolean isIconNeedToBeProcessed =
186                         SlicePreferencesUtil.isIconNeedsToBeProcessed(item);
187                 Drawable iconDrawable = icon.loadDrawable(contextThemeWrapper);
188                 if (isIconNeedToBeProcessed && isTwoPanel) {
189                     preference.setIcon(IconUtil.getCompoundIcon(contextThemeWrapper, iconDrawable));
190                 } else {
191                     preference.setIcon(iconDrawable);
192                 }
193             }
194 
195             if (data.mTitleItem != null) {
196                 preference.setTitle(getText(data.mTitleItem));
197             }
198 
199             //Set summary
200             CharSequence subtitle =
201                     data.mSubtitleItem != null ? data.mSubtitleItem.getText() : null;
202             boolean subtitleExists = !TextUtils.isEmpty(subtitle)
203                     || (data.mSubtitleItem != null && data.mSubtitleItem.hasHint(HINT_PARTIAL));
204             if (subtitleExists) {
205                 preference.setSummary(subtitle);
206             } else {
207                 if (data.mSummaryItem != null) {
208                     preference.setSummary(getText(data.mSummaryItem));
209                 }
210             }
211 
212             // Set preview info image and text
213             CharSequence infoText = getInfoText(item);
214             CharSequence infoSummary = getInfoSummary(item);
215             boolean addInfoStatus = addInfoStatus(item);
216             IconCompat infoImage = getInfoImage(item);
217             IconCompat infoTitleIcon = getInfoTitleIcon(item);
218             Bundle b = preference.getExtras();
219             String fallbackInfoContentDescription = "";
220             if (preference.getTitle() != null) {
221                 fallbackInfoContentDescription += preference.getTitle().toString();
222             }
223             if (infoImage != null) {
224                 b.putParcelable(EXTRA_PREFERENCE_INFO_IMAGE, infoImage.toIcon());
225             }
226             if (infoTitleIcon != null) {
227                 b.putParcelable(EXTRA_PREFERENCE_INFO_TITLE_ICON, infoTitleIcon.toIcon());
228             }
229             if (infoText != null) {
230                 if (preference instanceof SliceSwitchPreference && addInfoStatus) {
231                     b.putBoolean(InfoFragment.EXTRA_INFO_HAS_STATUS, true);
232                     b.putBoolean(EXTRA_PREFERENCE_INFO_STATUS,
233                             ((SliceSwitchPreference) preference).isChecked());
234                 } else {
235                     b.putBoolean(InfoFragment.EXTRA_INFO_HAS_STATUS, false);
236                 }
237                 b.putCharSequence(EXTRA_PREFERENCE_INFO_TEXT, infoText);
238                 if (preference.getTitle() != null
239                         && !preference.getTitle().equals(infoText.toString())) {
240                     fallbackInfoContentDescription +=
241                             CONTENT_DESCRIPTION_SEPARATOR + infoText.toString();
242                 }
243 
244             }
245             if (infoSummary != null) {
246                 b.putCharSequence(EXTRA_PREFERENCE_INFO_SUMMARY, infoSummary);
247                 fallbackInfoContentDescription +=
248                         CONTENT_DESCRIPTION_SEPARATOR + infoSummary.toString();
249             }
250             if (infoText != null || infoSummary != null) {
251                 if (preference instanceof SlicePreference) {
252                     ((SlicePreference) preference).setContentDescription(
253                             getInfoContentDescription(item, fallbackInfoContentDescription));
254                 } else if (preference instanceof SliceSwitchPreference) {
255                     ((SliceSwitchPreference) preference).setContentDescription(
256                             getInfoContentDescription(item, fallbackInfoContentDescription));
257                 } else if (preference instanceof CustomContentDescriptionPreference) {
258                     ((CustomContentDescriptionPreference) preference).setContentDescription(
259                             getInfoContentDescription(item, fallbackInfoContentDescription));
260                 }
261             }
262             if (infoImage != null || infoText != null || infoSummary != null) {
263                 preference.setFragment(InfoFragment.class.getCanonicalName());
264             }
265         }
266 
267         return preference;
268     }
269 
270     static class Data {
271         SliceItem mStartItem;
272         SliceItem mTitleItem;
273         SliceItem mSubtitleItem;
274         SliceItem mSummaryItem;
275         SliceItem mTargetSliceItem;
276         SliceItem mRadioGroupItem;
277         SliceItem mIntentItem;
278         SliceItem mFollowupIntentItem;
279         List<SliceItem> mEndItems = new ArrayList<>();
280         List<SliceItem> mInfoItems = new ArrayList<>();
281     }
282 
extract(SliceItem sliceItem)283     static Data extract(SliceItem sliceItem) {
284         Data data = new Data();
285         List<SliceItem> possibleStartItems =
286                 SliceQuery.findAll(sliceItem, null, HINT_TITLE, null);
287         if (possibleStartItems.size() > 0) {
288             // The start item will be at position 0 if it exists
289             String format = possibleStartItems.get(0).getFormat();
290             if ((FORMAT_ACTION.equals(format)
291                     && SliceQuery.find(possibleStartItems.get(0), FORMAT_IMAGE) != null)
292                     || FORMAT_SLICE.equals(format)
293                     || FORMAT_LONG.equals(format)
294                     || FORMAT_IMAGE.equals(format)) {
295                 data.mStartItem = possibleStartItems.get(0);
296             }
297         }
298 
299         List<SliceItem> items = sliceItem.getSlice().getItems();
300         for (int i = 0; i < items.size(); i++) {
301             final SliceItem item = items.get(i);
302             String subType = item.getSubType();
303             if (subType != null) {
304                 switch (subType) {
305                     case SlicesConstants.SUBTYPE_INFO_PREFERENCE :
306                         data.mInfoItems.add(item);
307                         break;
308                     case SlicesConstants.SUBTYPE_INTENT :
309                         data.mIntentItem = item;
310                         break;
311                     case SlicesConstants.SUBTYPE_FOLLOWUP_INTENT :
312                         data.mFollowupIntentItem = item;
313                         break;
314                     case SlicesConstants.TAG_TARGET_URI :
315                         data.mTargetSliceItem = item;
316                         break;
317                 }
318             } else if (FORMAT_TEXT.equals(item.getFormat()) && (item.getSubType() == null)) {
319                 if ((data.mTitleItem == null || !data.mTitleItem.hasHint(HINT_TITLE))
320                         && item.hasHint(HINT_TITLE) && !item.hasHint(HINT_SUMMARY)) {
321                     data.mTitleItem = item;
322                 } else if (data.mSubtitleItem == null && !item.hasHint(HINT_SUMMARY)) {
323                     data.mSubtitleItem = item;
324                 } else if (data.mSummaryItem == null && item.hasHint(HINT_SUMMARY)) {
325                     data.mSummaryItem = item;
326                 }
327             } else {
328                 data.mEndItems.add(item);
329             }
330         }
331         data.mEndItems.remove(data.mStartItem);
332         return data;
333     }
334 
getInfoList(List<SliceItem> sliceItems)335     private static List<Pair<CharSequence, CharSequence>> getInfoList(List<SliceItem> sliceItems) {
336         List<Pair<CharSequence, CharSequence>> infoList = new ArrayList<>();
337         for (SliceItem item : sliceItems) {
338             Slice itemSlice = item.getSlice();
339             if (itemSlice != null) {
340                 CharSequence title = null;
341                 CharSequence summary = null;
342                 for (SliceItem element : itemSlice.getItems()) {
343                     if (element.getHints().contains(HINT_TITLE)) {
344                         title = element.getText();
345                     } else if (element.getHints().contains(HINT_SUMMARY)) {
346                         summary = element.getText();
347                     }
348                 }
349                 infoList.add(new Pair<CharSequence, CharSequence>(title, summary));
350             }
351         }
352         return infoList;
353     }
354 
getKey(SliceItem item)355     private static CharSequence getKey(SliceItem item) {
356         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, SlicesConstants.TAG_KEY);
357         return target != null ? target.getText() : null;
358     }
359 
getRadioGroup(SliceItem item)360     private static CharSequence getRadioGroup(SliceItem item) {
361         SliceItem target = SliceQuery.findSubtype(
362                 item, FORMAT_TEXT, SlicesConstants.TAG_RADIO_GROUP);
363         return target != null ? target.getText() : null;
364     }
365 
366     /**
367      * Get the screen title item for the slice.
368      * @param sliceItems list of SliceItem extracted from slice data.
369      * @return screen title item.
370      */
getScreenTitleItem(List<SliceContent> sliceItems)371     static SliceItem getScreenTitleItem(List<SliceContent> sliceItems) {
372         for (SliceContent contentItem : sliceItems)  {
373             SliceItem item = contentItem.getSliceItem();
374             if (item.getSubType() != null
375                     && item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_SCREEN_TITLE)) {
376                 return item;
377             }
378         }
379         return null;
380     }
381 
getRedirectSlice(List<SliceContent> sliceItems)382     static SliceItem getRedirectSlice(List<SliceContent> sliceItems) {
383         for (SliceContent contentItem : sliceItems)  {
384             SliceItem item = contentItem.getSliceItem();
385             if (item.getSubType() != null
386                     && item.getSubType().equals(SlicesConstants.TYPE_REDIRECTED_SLICE_URI)) {
387                 return item;
388             }
389         }
390         return null;
391     }
392 
getFocusedPreferenceItem(List<SliceContent> sliceItems)393     static SliceItem getFocusedPreferenceItem(List<SliceContent> sliceItems) {
394         for (SliceContent contentItem : sliceItems)  {
395             SliceItem item = contentItem.getSliceItem();
396             if (item.getSubType() != null
397                     && item.getSubType().equals(SlicesConstants.TYPE_FOCUSED_PREFERENCE)) {
398                 return item;
399             }
400         }
401         return null;
402     }
403 
getEmbeddedItem(List<SliceContent> sliceItems)404     static SliceItem getEmbeddedItem(List<SliceContent> sliceItems) {
405         for (SliceContent contentItem : sliceItems)  {
406             SliceItem item = contentItem.getSliceItem();
407             if (item.getSubType() != null
408                     && item.getSubType().equals(SlicesConstants.TYPE_PREFERENCE_EMBEDDED)) {
409                 return item;
410             }
411         }
412         return null;
413     }
414 
isIconNeedsToBeProcessed(SliceItem sliceItem)415     private static boolean isIconNeedsToBeProcessed(SliceItem sliceItem) {
416         List<SliceItem> items = sliceItem.getSlice().getItems();
417         for (SliceItem item : items)  {
418             if (item.getSubType() != null && item.getSubType().equals(
419                     SlicesConstants.SUBTYPE_ICON_NEED_TO_BE_PROCESSED)) {
420                 return item.getInt() == 1;
421             }
422         }
423         return false;
424     }
425 
getButtonStyle(SliceItem sliceItem)426     private static int getButtonStyle(SliceItem sliceItem) {
427         List<SliceItem> items = sliceItem.getSlice().getItems();
428         for (SliceItem item : items)  {
429             if (item.getSubType() != null
430                     && item.getSubType().equals(SlicesConstants.SUBTYPE_BUTTON_STYLE)) {
431                 return item.getInt();
432             }
433         }
434         return -1;
435     }
436 
getSeekbarMin(SliceItem sliceItem)437     private static int getSeekbarMin(SliceItem sliceItem) {
438         List<SliceItem> items = sliceItem.getSlice().getItems();
439         for (SliceItem item : items)  {
440             if (item.getSubType() != null
441                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_MIN)) {
442                 return item.getInt();
443             }
444         }
445         return -1;
446     }
447 
getSeekbarMax(SliceItem sliceItem)448     private static int getSeekbarMax(SliceItem sliceItem) {
449         List<SliceItem> items = sliceItem.getSlice().getItems();
450         for (SliceItem item : items)  {
451             if (item.getSubType() != null
452                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_MAX)) {
453                 return item.getInt();
454             }
455         }
456         return -1;
457     }
458 
getSeekbarValue(SliceItem sliceItem)459     private static int getSeekbarValue(SliceItem sliceItem) {
460         List<SliceItem> items = sliceItem.getSlice().getItems();
461         for (SliceItem item : items)  {
462             if (item.getSubType() != null
463                     && item.getSubType().equals(SlicesConstants.SUBTYPE_SEEKBAR_VALUE)) {
464                 return item.getInt();
465             }
466         }
467         return -1;
468     }
469 
enabled(SliceItem sliceItem)470     private static boolean enabled(SliceItem sliceItem) {
471         List<SliceItem> items = sliceItem.getSlice().getItems();
472         for (SliceItem item : items)  {
473             if (item.getSubType() != null
474                     && item.getSubType().equals(SlicesConstants.SUBTYPE_IS_ENABLED)) {
475                 return item.getInt() == 1;
476             }
477         }
478         return true;
479     }
480 
selectable(SliceItem sliceItem)481     private static boolean selectable(SliceItem sliceItem) {
482         List<SliceItem> items = sliceItem.getSlice().getItems();
483         for (SliceItem item : items)  {
484             if (item.getSubType() != null
485                     && item.getSubType().equals(SlicesConstants.SUBTYPE_IS_SELECTABLE)) {
486                 return item.getInt() == 1;
487             }
488         }
489         return true;
490     }
491 
addInfoStatus(SliceItem sliceItem)492     private static boolean addInfoStatus(SliceItem sliceItem) {
493         List<SliceItem> items = sliceItem.getSlice().getItems();
494         for (SliceItem item : items)  {
495             if (item.getSubType() != null
496                     && item.getSubType().equals(EXTRA_ADD_INFO_STATUS)) {
497                 return item.getInt() == 1;
498             }
499         }
500         return true;
501     }
502 
503     /**
504      * Get the text from the SliceItem.
505      */
getText(SliceItem item)506     static CharSequence getText(SliceItem item) {
507         if (item == null) {
508             return null;
509         }
510         return item.getText();
511     }
512 
513     /** Get the icon from the SliceItem if available */
getIcon(SliceItem startItem)514     static Icon getIcon(SliceItem startItem) {
515         if (startItem != null && startItem.getSlice() != null
516                 && startItem.getSlice().getItems() != null
517                 && startItem.getSlice().getItems().size() > 0) {
518             SliceItem iconItem = startItem.getSlice().getItems().get(0);
519             if (FORMAT_IMAGE.equals(iconItem.getFormat())) {
520                 IconCompat icon = iconItem.getIcon();
521                 return icon.toIcon();
522             }
523         }
524         return null;
525     }
526 
getStatusPath(String uriString)527     static Uri getStatusPath(String uriString) {
528         Uri statusUri = Uri.parse(uriString)
529                 .buildUpon().path("/" + SlicesConstants.PATH_STATUS).build();
530         return statusUri;
531     }
532 
getPageId(SliceItem item)533     static int getPageId(SliceItem item) {
534         SliceItem target = SliceQuery.findSubtype(item, FORMAT_INT, EXTRA_PAGE_ID);
535         return target != null ? target.getInt() : 0;
536     }
537 
getActionId(SliceItem item)538     private static int getActionId(SliceItem item) {
539         SliceItem target = SliceQuery.findSubtype(item, FORMAT_INT, EXTRA_ACTION_ID);
540         return target != null ? target.getInt() : 0;
541     }
542 
543 
getInfoText(SliceItem item)544     private static CharSequence getInfoText(SliceItem item) {
545         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, EXTRA_PREFERENCE_INFO_TEXT);
546         return target != null ? target.getText() : null;
547     }
548 
getInfoSummary(SliceItem item)549     private static CharSequence getInfoSummary(SliceItem item) {
550         SliceItem target = SliceQuery.findSubtype(item, FORMAT_TEXT, EXTRA_PREFERENCE_INFO_SUMMARY);
551         return target != null ? target.getText() : null;
552     }
553 
getInfoImage(SliceItem item)554     private static IconCompat getInfoImage(SliceItem item) {
555         SliceItem target = SliceQuery.findSubtype(item, FORMAT_IMAGE, EXTRA_PREFERENCE_INFO_IMAGE);
556         return target != null ? target.getIcon() : null;
557     }
558 
getInfoTitleIcon(SliceItem item)559     private static IconCompat getInfoTitleIcon(SliceItem item) {
560         SliceItem target = SliceQuery.findSubtype(
561                 item, FORMAT_IMAGE, EXTRA_PREFERENCE_INFO_TITLE_ICON);
562         return target != null ? target.getIcon() : null;
563     }
564 
565     /**
566      * Get the content description from SliceItem if available
567      */
getInfoContentDescription( SliceItem sliceItem, String contentDescription)568     private static String getInfoContentDescription(
569             SliceItem sliceItem, String contentDescription) {
570         List<SliceItem> items = sliceItem.getSlice().getItems();
571         for (SliceItem item : items)  {
572             if (item.getSubType() != null
573                     && item.getSubType().equals(SUBTYPE_CONTENT_DESCRIPTION)) {
574                 return item.getText().toString();
575             }
576         }
577         return contentDescription;
578     }
579 }
580