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