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
17package androidx.slice.builders.impl;
18
19import static android.app.slice.Slice.HINT_ACTIONS;
20import static android.app.slice.Slice.HINT_LARGE;
21import static android.app.slice.Slice.HINT_LIST_ITEM;
22import static android.app.slice.Slice.HINT_NO_TINT;
23import static android.app.slice.Slice.HINT_PARTIAL;
24import static android.app.slice.Slice.HINT_SEE_MORE;
25import static android.app.slice.Slice.HINT_SHORTCUT;
26import static android.app.slice.Slice.HINT_SUMMARY;
27import static android.app.slice.Slice.HINT_TITLE;
28import static android.app.slice.Slice.SUBTYPE_COLOR;
29import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION;
30import static android.app.slice.Slice.SUBTYPE_MAX;
31import static android.app.slice.Slice.SUBTYPE_RANGE;
32import static android.app.slice.Slice.SUBTYPE_VALUE;
33import static android.app.slice.SliceItem.FORMAT_TEXT;
34
35import static androidx.annotation.RestrictTo.Scope.LIBRARY;
36import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
37import static androidx.slice.builders.ListBuilder.INFINITY;
38import static androidx.slice.builders.ListBuilder.LARGE_IMAGE;
39import static androidx.slice.core.SliceHints.HINT_KEYWORDS;
40import static androidx.slice.core.SliceHints.HINT_LAST_UPDATED;
41import static androidx.slice.core.SliceHints.HINT_TTL;
42import static androidx.slice.core.SliceHints.SUBTYPE_MILLIS;
43import static androidx.slice.core.SliceHints.SUBTYPE_MIN;
44
45import android.app.PendingIntent;
46import android.net.Uri;
47
48import androidx.annotation.ColorInt;
49import androidx.annotation.NonNull;
50import androidx.annotation.RestrictTo;
51import androidx.core.graphics.drawable.IconCompat;
52import androidx.slice.Slice;
53import androidx.slice.SliceItem;
54import androidx.slice.SliceSpec;
55import androidx.slice.builders.SliceAction;
56
57import java.util.ArrayList;
58import java.util.List;
59
60/**
61 * @hide
62 */
63@RestrictTo(LIBRARY)
64public 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