1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.slice.builders.impl; 18 19 import static android.app.slice.Slice.HINT_ACTIONS; 20 import static android.app.slice.Slice.HINT_LARGE; 21 import static android.app.slice.Slice.HINT_LIST_ITEM; 22 import static android.app.slice.Slice.HINT_NO_TINT; 23 import static android.app.slice.Slice.HINT_PARTIAL; 24 import static android.app.slice.Slice.HINT_SEE_MORE; 25 import static android.app.slice.Slice.HINT_SHORTCUT; 26 import static android.app.slice.Slice.HINT_SUMMARY; 27 import static android.app.slice.Slice.HINT_TITLE; 28 import static android.app.slice.Slice.SUBTYPE_COLOR; 29 import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION; 30 import static android.app.slice.Slice.SUBTYPE_MAX; 31 import static android.app.slice.Slice.SUBTYPE_RANGE; 32 import static android.app.slice.Slice.SUBTYPE_VALUE; 33 import static android.app.slice.SliceItem.FORMAT_TEXT; 34 35 import static androidx.annotation.RestrictTo.Scope.LIBRARY; 36 import static androidx.slice.builders.ListBuilder.ICON_IMAGE; 37 import static androidx.slice.builders.ListBuilder.INFINITY; 38 import static androidx.slice.builders.ListBuilder.LARGE_IMAGE; 39 import static androidx.slice.core.SliceHints.HINT_KEYWORDS; 40 import static androidx.slice.core.SliceHints.HINT_LAST_UPDATED; 41 import static androidx.slice.core.SliceHints.HINT_TTL; 42 import static androidx.slice.core.SliceHints.SUBTYPE_MILLIS; 43 import static androidx.slice.core.SliceHints.SUBTYPE_MIN; 44 45 import android.app.PendingIntent; 46 import android.net.Uri; 47 48 import androidx.annotation.ColorInt; 49 import androidx.annotation.NonNull; 50 import androidx.annotation.RestrictTo; 51 import androidx.core.graphics.drawable.IconCompat; 52 import androidx.slice.Slice; 53 import androidx.slice.SliceItem; 54 import androidx.slice.SliceSpec; 55 import androidx.slice.builders.SliceAction; 56 57 import java.util.ArrayList; 58 import java.util.List; 59 60 /** 61 * @hide 62 */ 63 @RestrictTo(LIBRARY) 64 public class ListBuilderV1Impl extends TemplateBuilderImpl implements ListBuilder { 65 66 private List<Slice> mSliceActions; 67 private Slice mSliceHeader; 68 69 /** 70 */ 71 public ListBuilderV1Impl(Slice.Builder b, SliceSpec spec) { 72 super(b, spec); 73 } 74 75 /** 76 */ 77 @Override 78 public void apply(Slice.Builder builder) { 79 builder.addLong(System.currentTimeMillis(), SUBTYPE_MILLIS, HINT_LAST_UPDATED); 80 if (mSliceHeader != null) { 81 builder.addSubSlice(mSliceHeader); 82 } 83 if (mSliceActions != null) { 84 Slice.Builder sb = new Slice.Builder(builder); 85 for (int i = 0; i < mSliceActions.size(); i++) { 86 sb.addSubSlice(mSliceActions.get(i)); 87 } 88 builder.addSubSlice(sb.addHints(HINT_ACTIONS).build()); 89 } 90 } 91 92 /** 93 * Add a row to list builder. 94 */ 95 @NonNull 96 @Override 97 public void addRow(@NonNull TemplateBuilderImpl builder) { 98 builder.getBuilder().addHints(HINT_LIST_ITEM); 99 getBuilder().addSubSlice(builder.build()); 100 } 101 102 /** 103 */ 104 @NonNull 105 @Override 106 public void addGridRow(@NonNull TemplateBuilderImpl builder) { 107 builder.getBuilder().addHints(HINT_LIST_ITEM); 108 getBuilder().addSubSlice(builder.build()); 109 } 110 111 /** 112 */ 113 @Override 114 public void setHeader(@NonNull TemplateBuilderImpl builder) { 115 mSliceHeader = builder.build(); 116 } 117 118 /** 119 */ 120 @Override 121 public void addAction(@NonNull SliceAction action) { 122 if (mSliceActions == null) { 123 mSliceActions = new ArrayList<>(); 124 } 125 Slice.Builder b = new Slice.Builder(getBuilder()).addHints(HINT_ACTIONS); 126 mSliceActions.add(action.buildSlice(b)); 127 } 128 129 @Override 130 public void addInputRange(TemplateBuilderImpl builder) { 131 getBuilder().addSubSlice(builder.build(), SUBTYPE_RANGE); 132 } 133 134 @Override 135 public void addRange(TemplateBuilderImpl builder) { 136 getBuilder().addSubSlice(builder.build(), SUBTYPE_RANGE); 137 } 138 139 /** 140 */ 141 @Override 142 public void setSeeMoreRow(TemplateBuilderImpl builder) { 143 builder.getBuilder().addHints(HINT_SEE_MORE); 144 getBuilder().addSubSlice(builder.build()); 145 } 146 147 /** 148 */ 149 @Override 150 public void setSeeMoreAction(PendingIntent intent) { 151 getBuilder().addSubSlice( 152 new Slice.Builder(getBuilder()) 153 .addHints(HINT_SEE_MORE) 154 .addAction(intent, new Slice.Builder(getBuilder()) 155 .addHints(HINT_SEE_MORE).build(), null) 156 .build()); 157 } 158 159 160 /** 161 * Builder to construct an input row. 162 */ 163 public static class RangeBuilderImpl extends TemplateBuilderImpl implements RangeBuilder { 164 private int mMin = 0; 165 private int mMax = 100; 166 private int mValue = 0; 167 private CharSequence mTitle; 168 private CharSequence mSubtitle; 169 private CharSequence mContentDescr; 170 private SliceAction mPrimaryAction; 171 172 public RangeBuilderImpl(Slice.Builder sb) { 173 super(sb, null); 174 } 175 176 @Override 177 public void setMin(int min) { 178 mMin = min; 179 } 180 181 @Override 182 public void setMax(int max) { 183 mMax = max; 184 } 185 186 @Override 187 public void setValue(int value) { 188 mValue = value; 189 } 190 191 @Override 192 public void setTitle(@NonNull CharSequence title) { 193 mTitle = title; 194 } 195 196 @Override 197 public void setSubtitle(@NonNull CharSequence title) { 198 mSubtitle = title; 199 } 200 201 @Override 202 public void setPrimaryAction(@NonNull SliceAction action) { 203 mPrimaryAction = action; 204 } 205 206 @Override 207 public void setContentDescription(@NonNull CharSequence description) { 208 mContentDescr = description; 209 } 210 211 @Override 212 public void apply(Slice.Builder builder) { 213 if (mTitle != null) { 214 builder.addText(mTitle, null, HINT_TITLE); 215 } 216 if (mSubtitle != null) { 217 builder.addText(mSubtitle, null); 218 } 219 if (mContentDescr != null) { 220 builder.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION); 221 } 222 if (mPrimaryAction != null) { 223 Slice.Builder sb = new Slice.Builder( 224 getBuilder()).addHints(HINT_TITLE, HINT_SHORTCUT); 225 builder.addSubSlice(mPrimaryAction.buildSlice(sb), null /* subtype */); 226 } 227 builder.addHints(HINT_LIST_ITEM) 228 .addInt(mMin, SUBTYPE_MIN) 229 .addInt(mMax, SUBTYPE_MAX) 230 .addInt(mValue, SUBTYPE_VALUE); 231 } 232 } 233 234 /** 235 * Builder to construct an input range row. 236 */ 237 public static class InputRangeBuilderImpl 238 extends RangeBuilderImpl implements InputRangeBuilder { 239 private PendingIntent mAction; 240 private IconCompat mThumb; 241 242 public InputRangeBuilderImpl(Slice.Builder sb) { 243 super(sb); 244 } 245 246 @Override 247 public void setInputAction(@NonNull PendingIntent action) { 248 mAction = action; 249 } 250 251 @Override 252 public void setThumb(@NonNull IconCompat thumb) { 253 mThumb = thumb; 254 } 255 256 @Override 257 public void apply(Slice.Builder builder) { 258 if (mAction == null) { 259 throw new IllegalStateException("Input ranges must have an associated action."); 260 } 261 Slice.Builder sb = new Slice.Builder(builder); 262 super.apply(sb); 263 if (mThumb != null) { 264 sb.addIcon(mThumb, null); 265 } 266 builder.addAction(mAction, sb.build(), SUBTYPE_RANGE).addHints(HINT_LIST_ITEM); 267 } 268 } 269 270 /** 271 */ 272 @NonNull 273 @Override 274 public void setColor(@ColorInt int color) { 275 getBuilder().addInt(color, SUBTYPE_COLOR); 276 } 277 278 /** 279 */ 280 @Override 281 public void setKeywords(@NonNull List<String> keywords) { 282 Slice.Builder sb = new Slice.Builder(getBuilder()); 283 for (int i = 0; i < keywords.size(); i++) { 284 sb.addText(keywords.get(i), null); 285 } 286 getBuilder().addSubSlice(sb.addHints(HINT_KEYWORDS).build()); 287 } 288 289 /** 290 */ 291 @Override 292 public void setTtl(long ttl) { 293 long expiry = ttl == INFINITY ? INFINITY : System.currentTimeMillis() + ttl; 294 getBuilder().addTimestamp(expiry, SUBTYPE_MILLIS, HINT_TTL); 295 } 296 297 /** 298 */ 299 @Override 300 public TemplateBuilderImpl createRowBuilder() { 301 return new RowBuilderImpl(this); 302 } 303 304 /** 305 */ 306 @Override 307 public TemplateBuilderImpl createRowBuilder(Uri uri) { 308 return new RowBuilderImpl(uri); 309 } 310 311 312 @Override 313 public TemplateBuilderImpl createInputRangeBuilder() { 314 return new InputRangeBuilderImpl(createChildBuilder()); 315 } 316 317 @Override 318 public TemplateBuilderImpl createRangeBuilder() { 319 return new RangeBuilderImpl(createChildBuilder()); 320 } 321 322 /** 323 */ 324 @Override 325 public TemplateBuilderImpl createGridBuilder() { 326 return new GridRowBuilderListV1Impl(this); 327 } 328 329 /** 330 */ 331 @Override 332 public TemplateBuilderImpl createHeaderBuilder() { 333 return new HeaderBuilderImpl(this); 334 } 335 336 @Override 337 public TemplateBuilderImpl createHeaderBuilder(Uri uri) { 338 return new HeaderBuilderImpl(uri); 339 } 340 341 /** 342 */ 343 public static class RowBuilderImpl extends TemplateBuilderImpl 344 implements ListBuilder.RowBuilder { 345 346 private SliceAction mPrimaryAction; 347 private SliceItem mTitleItem; 348 private SliceItem mSubtitleItem; 349 private Slice mStartItem; 350 private ArrayList<Slice> mEndItems = new ArrayList<>(); 351 private CharSequence mContentDescr; 352 353 /** 354 */ 355 public RowBuilderImpl(@NonNull ListBuilderV1Impl parent) { 356 super(parent.createChildBuilder(), null); 357 } 358 359 /** 360 */ 361 public RowBuilderImpl(@NonNull Uri uri) { 362 super(new Slice.Builder(uri), null); 363 } 364 365 /** 366 */ 367 public RowBuilderImpl(Slice.Builder builder) { 368 super(builder, null); 369 } 370 371 /** 372 */ 373 @NonNull 374 @Override 375 public void setTitleItem(long timeStamp) { 376 mStartItem = new Slice.Builder(getBuilder()) 377 .addTimestamp(timeStamp, null).addHints(HINT_TITLE).build(); 378 } 379 380 /** 381 */ 382 @NonNull 383 @Override 384 public void setTitleItem(IconCompat icon, int imageMode) { 385 setTitleItem(icon, imageMode, false /* isLoading */); 386 } 387 388 /** 389 */ 390 @NonNull 391 @Override 392 public void setTitleItem(IconCompat icon, int imageMode, boolean isLoading) { 393 ArrayList<String> hints = new ArrayList<>(); 394 if (imageMode != ICON_IMAGE) { 395 hints.add(HINT_NO_TINT); 396 } 397 if (imageMode == LARGE_IMAGE) { 398 hints.add(HINT_LARGE); 399 } 400 if (isLoading) { 401 hints.add(HINT_PARTIAL); 402 } 403 Slice.Builder sb = new Slice.Builder(getBuilder()) 404 .addIcon(icon, null /* subType */, hints); 405 if (isLoading) { 406 sb.addHints(HINT_PARTIAL); 407 } 408 mStartItem = sb.addHints(HINT_TITLE).build(); 409 } 410 411 /** 412 */ 413 @NonNull 414 @Override 415 public void setTitleItem(@NonNull SliceAction action) { 416 setTitleItem(action, false /* isLoading */); 417 } 418 419 /** 420 */ 421 @Override 422 public void setTitleItem(SliceAction action, boolean isLoading) { 423 Slice.Builder sb = new Slice.Builder(getBuilder()).addHints(HINT_TITLE); 424 if (isLoading) { 425 sb.addHints(HINT_PARTIAL); 426 } 427 mStartItem = action.buildSlice(sb); 428 } 429 430 /** 431 */ 432 @NonNull 433 @Override 434 public void setPrimaryAction(@NonNull SliceAction action) { 435 mPrimaryAction = action; 436 } 437 438 /** 439 */ 440 @NonNull 441 @Override 442 public void setTitle(CharSequence title) { 443 setTitle(title, false /* isLoading */); 444 } 445 446 /** 447 */ 448 @Override 449 public void setTitle(CharSequence title, boolean isLoading) { 450 mTitleItem = new SliceItem(title, FORMAT_TEXT, null, new String[] {HINT_TITLE}); 451 if (isLoading) { 452 mTitleItem.addHint(HINT_PARTIAL); 453 } 454 } 455 456 /** 457 */ 458 @NonNull 459 @Override 460 public void setSubtitle(CharSequence subtitle) { 461 setSubtitle(subtitle, false /* isLoading */); 462 } 463 464 /** 465 */ 466 @Override 467 public void setSubtitle(CharSequence subtitle, boolean isLoading) { 468 mSubtitleItem = new SliceItem(subtitle, FORMAT_TEXT, null, new String[0]); 469 if (isLoading) { 470 mSubtitleItem.addHint(HINT_PARTIAL); 471 } 472 } 473 474 /** 475 */ 476 @NonNull 477 @Override 478 public void addEndItem(long timeStamp) { 479 mEndItems.add(new Slice.Builder(getBuilder()).addTimestamp(timeStamp, 480 null, new String[0]).build()); 481 } 482 483 /** 484 */ 485 @NonNull 486 @Override 487 public void addEndItem(IconCompat icon, int imageMode) { 488 addEndItem(icon, imageMode, false /* isLoading */); 489 } 490 491 /** 492 */ 493 @NonNull 494 @Override 495 public void addEndItem(IconCompat icon, int imageMode, boolean isLoading) { 496 ArrayList<String> hints = new ArrayList<>(); 497 if (imageMode != ICON_IMAGE) { 498 hints.add(HINT_NO_TINT); 499 } 500 if (imageMode == LARGE_IMAGE) { 501 hints.add(HINT_LARGE); 502 } 503 if (isLoading) { 504 hints.add(HINT_PARTIAL); 505 } 506 Slice.Builder sb = new Slice.Builder(getBuilder()) 507 .addIcon(icon, null /* subType */, hints); 508 if (isLoading) { 509 sb.addHints(HINT_PARTIAL); 510 } 511 mEndItems.add(sb.build()); 512 } 513 514 /** 515 */ 516 @NonNull 517 @Override 518 public void addEndItem(@NonNull SliceAction action) { 519 addEndItem(action, false /* isLoading */); 520 } 521 522 /** 523 */ 524 @Override 525 public void addEndItem(@NonNull SliceAction action, boolean isLoading) { 526 Slice.Builder sb = new Slice.Builder(getBuilder()); 527 if (isLoading) { 528 sb.addHints(HINT_PARTIAL); 529 } 530 mEndItems.add(action.buildSlice(sb)); 531 } 532 533 @Override 534 public void setContentDescription(CharSequence description) { 535 mContentDescr = description; 536 } 537 538 /** 539 */ 540 @Override 541 public void apply(Slice.Builder b) { 542 if (mStartItem != null) { 543 b.addSubSlice(mStartItem); 544 } 545 if (mTitleItem != null) { 546 b.addItem(mTitleItem); 547 } 548 if (mSubtitleItem != null) { 549 b.addItem(mSubtitleItem); 550 } 551 for (int i = 0; i < mEndItems.size(); i++) { 552 Slice item = mEndItems.get(i); 553 b.addSubSlice(item); 554 } 555 if (mContentDescr != null) { 556 b.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION); 557 } 558 if (mPrimaryAction != null) { 559 Slice.Builder sb = new Slice.Builder( 560 getBuilder()).addHints(HINT_TITLE, HINT_SHORTCUT); 561 b.addSubSlice(mPrimaryAction.buildSlice(sb), null); 562 } 563 } 564 } 565 566 /** 567 */ 568 public static class HeaderBuilderImpl extends TemplateBuilderImpl 569 implements ListBuilder.HeaderBuilder { 570 571 private SliceItem mTitleItem; 572 private SliceItem mSubtitleItem; 573 private SliceItem mSummaryItem; 574 private SliceAction mPrimaryAction; 575 private CharSequence mContentDescr; 576 577 /** 578 */ 579 public HeaderBuilderImpl(@NonNull ListBuilderV1Impl parent) { 580 super(parent.createChildBuilder(), null); 581 } 582 583 /** 584 */ 585 public HeaderBuilderImpl(@NonNull Uri uri) { 586 super(new Slice.Builder(uri), null); 587 } 588 589 /** 590 */ 591 @Override 592 public void apply(Slice.Builder b) { 593 if (mTitleItem != null) { 594 b.addItem(mTitleItem); 595 } 596 if (mSubtitleItem != null) { 597 b.addItem(mSubtitleItem); 598 } 599 if (mSummaryItem != null) { 600 b.addItem(mSummaryItem); 601 } 602 if (mContentDescr != null) { 603 b.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION); 604 } 605 if (mPrimaryAction != null) { 606 Slice.Builder sb = new Slice.Builder( 607 getBuilder()).addHints(HINT_TITLE, HINT_SHORTCUT); 608 b.addSubSlice(mPrimaryAction.buildSlice(sb), null /* subtype */); 609 } 610 } 611 612 /** 613 */ 614 @Override 615 public void setTitle(CharSequence title, boolean isLoading) { 616 mTitleItem = new SliceItem(title, FORMAT_TEXT, null, new String[] {HINT_TITLE}); 617 if (isLoading) { 618 mTitleItem.addHint(HINT_PARTIAL); 619 } 620 } 621 622 /** 623 */ 624 @Override 625 public void setSubtitle(CharSequence subtitle, boolean isLoading) { 626 mSubtitleItem = new SliceItem(subtitle, FORMAT_TEXT, null, new String[0]); 627 if (isLoading) { 628 mSubtitleItem.addHint(HINT_PARTIAL); 629 } 630 } 631 632 /** 633 */ 634 @Override 635 public void setSummary(CharSequence summarySubtitle, boolean isLoading) { 636 mSummaryItem = new SliceItem(summarySubtitle, FORMAT_TEXT, null, 637 new String[] {HINT_SUMMARY}); 638 if (isLoading) { 639 mSummaryItem.addHint(HINT_PARTIAL); 640 } 641 } 642 643 /** 644 */ 645 @Override 646 public void setPrimaryAction(SliceAction action) { 647 mPrimaryAction = action; 648 } 649 650 /** 651 */ 652 @Override 653 public void setContentDescription(CharSequence description) { 654 mContentDescr = description; 655 } 656 } 657 } 658