SampleSliceProvider.java revision 55312b3aeb642f17fc373aab94182b3456e3ac47
1/*
2 * Copyright 2017 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 com.example.androidx.slice.demos;
18
19import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
20
21import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
22import static androidx.slice.builders.ListBuilder.LARGE_IMAGE;
23import static androidx.slice.builders.ListBuilder.SMALL_IMAGE;
24
25import android.app.PendingIntent;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.graphics.drawable.Icon;
30import android.net.Uri;
31import android.net.wifi.WifiManager;
32import android.os.Handler;
33import android.provider.Settings;
34import android.support.annotation.NonNull;
35import android.text.SpannableString;
36import android.text.format.DateUtils;
37import android.text.style.ForegroundColorSpan;
38
39import java.util.Calendar;
40
41import androidx.slice.Slice;
42import androidx.slice.SliceProvider;
43import androidx.slice.builders.GridBuilder;
44import androidx.slice.builders.ListBuilder;
45import androidx.slice.builders.MessagingSliceBuilder;
46import androidx.slice.builders.SliceAction;
47
48/**
49 * Examples of using slice template builders.
50 */
51public class SampleSliceProvider extends SliceProvider {
52
53    private static final boolean TEST_CUSTOM_SEE_MORE = false;
54
55    public static final String ACTION_WIFI_CHANGED =
56            "com.example.androidx.slice.action.WIFI_CHANGED";
57    public static final String ACTION_TOAST =
58            "com.example.androidx.slice.action.TOAST";
59    public static final String EXTRA_TOAST_MESSAGE = "com.example.androidx.extra.TOAST_MESSAGE";
60    public static final String ACTION_TOAST_RANGE_VALUE =
61            "com.example.androidx.slice.action.TOAST_RANGE_VALUE";
62
63    public static final int LOADING_DELAY_MS = 4000;
64
65    public static final String[] URI_PATHS = {"message", "wifi", "note", "ride", "toggle",
66            "toggle2", "contact", "gallery", "weather", "reservation", "loadlist", "loadlist2",
67            "loadgrid", "loadgrid2", "inputrange", "range", "contact2", "subscription"};
68
69    /**
70     * @return Uri with the provided path
71     */
72    public static Uri getUri(String path, Context context) {
73        return new Uri.Builder()
74                .scheme(ContentResolver.SCHEME_CONTENT)
75                .authority(context.getPackageName())
76                .appendPath(path)
77                .build();
78    }
79
80    @Override
81    public boolean onCreateSliceProvider() {
82        return true;
83    }
84
85    @NonNull
86    @Override
87    public Uri onMapIntentToUri(Intent intent) {
88        return getUri("wifi", getContext());
89    }
90
91    @Override
92    public Slice onBindSlice(Uri sliceUri) {
93        String path = sliceUri.getPath();
94        switch (path) {
95            // TODO: add list / grid slices with 'see more' options
96            case "/message":
97                return createMessagingSlice(sliceUri);
98            case "/wifi":
99                return createWifiSlice(sliceUri);
100            case "/note":
101                return createNoteSlice(sliceUri);
102            case "/ride":
103                return createRideSlice(sliceUri);
104            case "/toggle":
105                return createCustomToggleSlice(sliceUri);
106            case "/toggle2":
107                return createTwoCustomToggleSlices(sliceUri);
108            case "/contact":
109                return createContact(sliceUri);
110            case "/contact2":
111                return createContact2(sliceUri);
112            case "/gallery":
113                return createGallery(sliceUri);
114            case "/weather":
115                return createWeather(sliceUri);
116            case "/reservation":
117                return createReservationSlice(sliceUri);
118            case "/loadlist":
119                return createLoadingSlice(sliceUri, false /* loadAll */, true /* isList */);
120            case "/loadlist2":
121                return createLoadingSlice(sliceUri, true /* loadAll */, true /* isList */);
122            case "/loadgrid":
123                return createLoadingSlice(sliceUri, false /* loadAll */, false /* isList */);
124            case "/loadgrid2":
125                return createLoadingSlice(sliceUri, true /* loadAll */, false /* isList */);
126            case "/inputrange":
127                return createStarRatingInputRange(sliceUri);
128            case "/range":
129                return createDownloadProgressRange(sliceUri);
130            case "/subscription":
131                return createCatSlice(sliceUri, false /* customSeeMore */);
132        }
133        throw new IllegalArgumentException("Unknown uri " + sliceUri);
134    }
135
136    private Slice createWeather(Uri sliceUri) {
137        SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST,
138                "open weather app"), Icon.createWithResource(getContext(), R.drawable.weather_1),
139                "Weather is happening!");
140        return new ListBuilder(getContext(), sliceUri).addGrid(gb -> gb
141                .setPrimaryAction(primaryAction)
142                .addCell(cb -> cb
143                        .addImage(Icon.createWithResource(getContext(), R.drawable.weather_1),
144                                SMALL_IMAGE)
145                        .addText("MON")
146                        .addTitleText("69\u00B0"))
147                .addCell(cb -> cb
148                        .addImage(Icon.createWithResource(getContext(), R.drawable.weather_2),
149                                SMALL_IMAGE)
150                        .addText("TUE")
151                        .addTitleText("71\u00B0"))
152                .addCell(cb -> cb
153                        .addImage(Icon.createWithResource(getContext(), R.drawable.weather_3),
154                                SMALL_IMAGE)
155                        .addText("WED")
156                        .addTitleText("76\u00B0"))
157                .addCell(cb -> cb
158                        .addImage(Icon.createWithResource(getContext(), R.drawable.weather_4),
159                                SMALL_IMAGE)
160                        .addText("THU")
161                        .addTitleText("72\u00B0"))
162                .addCell(cb -> cb
163                        .addImage(Icon.createWithResource(getContext(), R.drawable.weather_1),
164                                SMALL_IMAGE)
165                        .addText("FRI")
166                        .addTitleText("68\u00B0")))
167                .build();
168    }
169
170    private Slice createGallery(Uri sliceUri) {
171        return new ListBuilder(getContext(), sliceUri)
172                .setColor(0xff4285F4)
173                .addRow(b -> b
174                    .setTitle("Family trip to Hawaii")
175                    .setSubtitle("Sep 30, 2017 - Oct 2, 2017"))
176                .addAction(new SliceAction(
177                        getBroadcastIntent(ACTION_TOAST, "cast photo album"),
178                        Icon.createWithResource(getContext(), R.drawable.ic_cast),
179                        "Cast photo album"))
180                .addAction(new SliceAction(
181                        getBroadcastIntent(ACTION_TOAST, "share photo album"),
182                        Icon.createWithResource(getContext(), R.drawable.ic_share),
183                        "Share photo album"))
184                .addGrid(b -> b
185                    .addCell(cb -> cb
186                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_1),
187                            LARGE_IMAGE))
188                    .addCell(cb -> cb
189                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_2),
190                                LARGE_IMAGE))
191                    .addCell(cb -> cb
192                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_3),
193                                LARGE_IMAGE))
194                    .addCell(cb -> cb
195                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_4),
196                                LARGE_IMAGE))
197                    .addCell(cb -> cb
198                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_2),
199                                LARGE_IMAGE))
200                    .addCell(cb -> cb
201                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_3),
202                                LARGE_IMAGE))
203                    .addCell(cb -> cb
204                        .addImage(Icon.createWithResource(getContext(), R.drawable.slices_4),
205                                LARGE_IMAGE))
206                    .addSeeMoreAction(getBroadcastIntent(ACTION_TOAST, "see your gallery")))
207                .build();
208    }
209
210    private Slice createCatSlice(Uri sliceUri, boolean customSeeMore) {
211        ListBuilder b = new ListBuilder(getContext(), sliceUri);
212        GridBuilder gb = new GridBuilder(b);
213        PendingIntent pi = getBroadcastIntent(ACTION_TOAST, "See cats you follow");
214        if (customSeeMore) {
215            GridBuilder.CellBuilder cb = new GridBuilder.CellBuilder(gb);
216            cb.addImage(Icon.createWithResource(getContext(), R.drawable.ic_right_caret),
217                    ICON_IMAGE);
218            cb.setContentIntent(pi);
219            cb.addTitleText("All cats");
220            gb.addSeeMoreCell(cb);
221        } else {
222            gb.addSeeMoreAction(pi);
223        }
224        gb.addCell(new GridBuilder.CellBuilder(gb)
225                    .addImage(Icon.createWithResource(getContext(), R.drawable.cat_1), SMALL_IMAGE)
226                    .addTitleText("Oreo"))
227                .addCell(new GridBuilder.CellBuilder(gb)
228                        .addImage(Icon.createWithResource(getContext(), R.drawable.cat_2),
229                                SMALL_IMAGE)
230                        .addTitleText("Silver"))
231                .addCell(new GridBuilder.CellBuilder(gb)
232                        .addImage(Icon.createWithResource(getContext(), R.drawable.cat_3),
233                                SMALL_IMAGE)
234                        .addTitleText("Drake"))
235                .addCell(new GridBuilder.CellBuilder(gb)
236                        .addImage(Icon.createWithResource(getContext(), R.drawable.cat_5),
237                                SMALL_IMAGE)
238                        .addTitleText("Olive"))
239                .addCell(new GridBuilder.CellBuilder(gb)
240                        .addImage(Icon.createWithResource(getContext(), R.drawable.cat_4),
241                                SMALL_IMAGE)
242                        .addTitleText("Lady Marmalade"))
243                .addCell(new GridBuilder.CellBuilder(gb)
244                        .addImage(Icon.createWithResource(getContext(), R.drawable.cat_6),
245                                SMALL_IMAGE)
246                        .addTitleText("Grapefruit"));
247        return b.addGrid(gb).build();
248    }
249
250    private Slice createContact2(Uri sliceUri) {
251        ListBuilder b = new ListBuilder(getContext(), sliceUri);
252        ListBuilder.RowBuilder rb = new ListBuilder.RowBuilder(b);
253        GridBuilder gb = new GridBuilder(b);
254        return b.setColor(0xff3949ab)
255                .addRow(rb
256                        .setTitle("Mady Pitza")
257                        .setSubtitle("Frequently contacted contact")
258                        .addEndItem(Icon.createWithResource(getContext(), R.drawable.mady)))
259                .addGrid(gb
260                        .addCell(new GridBuilder.CellBuilder(gb)
261                                .addImage(Icon.createWithResource(getContext(), R.drawable.ic_call),
262                                        ICON_IMAGE)
263                                .addText("Call")
264                                .setContentIntent(getBroadcastIntent(ACTION_TOAST, "call")))
265                        .addCell(new GridBuilder.CellBuilder(gb)
266                                .addImage(Icon.createWithResource(getContext(), R.drawable.ic_text),
267                                        ICON_IMAGE)
268                                .addText("Text")
269                                .setContentIntent(getBroadcastIntent(ACTION_TOAST, "text")))
270                        .addCell(new GridBuilder.CellBuilder(gb)
271                                .addImage(Icon.createWithResource(getContext(),
272                                        R.drawable.ic_video), ICON_IMAGE)
273                                .setContentIntent(getBroadcastIntent(ACTION_TOAST, "video"))
274                                .addText("Video"))
275                        .addCell(new GridBuilder.CellBuilder(gb)
276                                .addImage(Icon.createWithResource(getContext(),
277                                        R.drawable.ic_email), ICON_IMAGE)
278                                .addText("Email")
279                                .setContentIntent(getBroadcastIntent(ACTION_TOAST, "email"))))
280                .build();
281    }
282
283    private Slice createContact(Uri sliceUri) {
284        final long lastCalled = System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS;
285        CharSequence lastCalledString = DateUtils.getRelativeTimeSpanString(lastCalled,
286                Calendar.getInstance().getTimeInMillis(),
287                DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
288        SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST,
289                "See contact info"), Icon.createWithResource(getContext(),
290                R.drawable.mady), "Mady");
291        return new ListBuilder(getContext(), sliceUri)
292                .setColor(0xff3949ab)
293                .setHeader(b -> b
294                        .setTitle("Mady Pitza")
295                        .setSummarySubtitle("Called " + lastCalledString)
296                        .setPrimaryAction(primaryAction))
297                .addRow(b -> b
298                        .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_call))
299                        .setTitle("314-259-2653")
300                        .setSubtitle("Call lasted 1 hr 17 min")
301                        .addEndItem(lastCalled))
302                .addRow(b -> b
303                        .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_text))
304                        .setTitle("You: Coooooool see you then")
305                        .addEndItem(System.currentTimeMillis() - 40 * DateUtils.MINUTE_IN_MILLIS))
306                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "call"),
307                        Icon.createWithResource(getContext(), R.drawable.ic_call), "Call mady"))
308                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "text"),
309                        Icon.createWithResource(getContext(), R.drawable.ic_text), "Text mady"))
310                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "video"),
311                        Icon.createWithResource(getContext(), R.drawable.ic_video),
312                        "Video call mady"))
313                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "email"),
314                        Icon.createWithResource(getContext(), R.drawable.ic_email), "Email mady"))
315                .build();
316    }
317
318    private Slice createMessagingSlice(Uri sliceUri) {
319        // TODO: Remote input.
320        return new MessagingSliceBuilder(getContext(), sliceUri)
321                .add(b -> b
322                        .addText("yo home \uD83C\uDF55, I emailed you the info")
323                        .addTimestamp(System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS)
324                        .addSource(Icon.createWithResource(getContext(), R.drawable.mady)))
325                .add(b -> b
326                        .addText("just bought my tickets")
327                        .addTimestamp(System.currentTimeMillis() - 10 * DateUtils.MINUTE_IN_MILLIS))
328                .add(b -> b
329                        .addText("yay! can't wait for getContext() weekend!\n"
330                                + "\uD83D\uDE00")
331                        .addTimestamp(System.currentTimeMillis() - 5 * DateUtils.MINUTE_IN_MILLIS)
332                        .addSource(Icon.createWithResource(getContext(), R.drawable.mady)))
333                .build();
334
335    }
336
337    private Slice createNoteSlice(Uri sliceUri) {
338        // TODO: Remote input.
339        return new ListBuilder(getContext(), sliceUri)
340                .setColor(0xfff4b400)
341                .addRow(b -> b.setTitle("Create new note"))
342                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "create note"),
343                        Icon.createWithResource(getContext(), R.drawable.ic_create),
344                        "Create note"))
345                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "voice note"),
346                        Icon.createWithResource(getContext(), R.drawable.ic_voice),
347                        "Voice note"))
348                .addAction(new SliceAction(getIntent("android.media.action.IMAGE_CAPTURE"),
349                        Icon.createWithResource(getContext(), R.drawable.ic_camera),
350                        "Photo note"))
351                .build();
352    }
353
354    private Slice createReservationSlice(Uri sliceUri) {
355        return new ListBuilder(getContext(), sliceUri)
356                .setColor(0xffFF5252)
357                .setHeader(b -> b
358                    .setTitle("Upcoming trip to Seattle")
359                    .setSubtitle("Feb 1 - 19 | 2 guests"))
360                .addAction(new SliceAction(
361                        getBroadcastIntent(ACTION_TOAST, "show location on map"),
362                        Icon.createWithResource(getContext(), R.drawable.ic_location),
363                        "Show reservation location"))
364                .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "contact host"),
365                        Icon.createWithResource(getContext(), R.drawable.ic_text),
366                        "Contact host"))
367                .addGrid(b -> b
368                    .addCell(cb -> cb
369                        .addImage(Icon.createWithResource(getContext(), R.drawable.reservation),
370                            LARGE_IMAGE)))
371                .addGrid(b -> b
372                    .addCell(cb -> cb
373                        .addTitleText("Check In")
374                        .addText("12:00 PM, Feb 1"))
375                    .addCell(cb -> cb
376                        .addTitleText("Check Out")
377                        .addText("11:00 AM, Feb 19")))
378                .build();
379    }
380
381    private Slice createRideSlice(Uri sliceUri) {
382        final ForegroundColorSpan colorSpan = new ForegroundColorSpan(0xff0F9D58);
383        SpannableString headerSubtitle = new SpannableString("Ride in 4 min");
384        headerSubtitle.setSpan(colorSpan, 8, headerSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
385        SpannableString homeSubtitle = new SpannableString("12 miles | 12 min | $9.00");
386        homeSubtitle.setSpan(colorSpan, 20, homeSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
387        SpannableString workSubtitle = new SpannableString("44 miles | 1 hour 45 min | $31.41");
388        workSubtitle.setSpan(colorSpan, 27, workSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
389
390        SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, "get ride"),
391                Icon.createWithResource(getContext(), R.drawable.ic_car), "Get Ride");
392        return new ListBuilder(getContext(), sliceUri)
393                .setColor(0xff0F9D58)
394                .setHeader(b -> b
395                    .setTitle("Get ride")
396                    .setSubtitle(headerSubtitle)
397                    .setSummarySubtitle("Ride to work in 12 min | Ride home in 1 hour 45 min")
398                    .setPrimaryAction(primaryAction))
399                .addRow(b -> b
400                    .setTitle("Work")
401                    .setSubtitle(workSubtitle)
402                    .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "work"),
403                            Icon.createWithResource(getContext(), R.drawable.ic_work),
404                            "Get ride to work")))
405                .addRow(b -> b
406                    .setTitle("Home")
407                    .setSubtitle(homeSubtitle)
408                    .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "home"),
409                            Icon.createWithResource(getContext(), R.drawable.ic_home),
410                            "Get ride home")))
411                .build();
412    }
413
414    private Slice createCustomToggleSlice(Uri sliceUri) {
415        return new ListBuilder(getContext(), sliceUri)
416                .setColor(0xffff4081)
417                .addRow(b -> b
418                    .setTitle("Custom toggle")
419                    .setSubtitle("It can support two states")
420                    .setPrimaryAction(new SliceAction(getBroadcastIntent(ACTION_TOAST,
421                            "star toggled"),
422                            Icon.createWithResource(getContext(), R.drawable.toggle_star),
423                            "Toggle star", true /* isChecked */)))
424                .build();
425    }
426
427    private Slice createTwoCustomToggleSlices(Uri sliceUri) {
428        return new ListBuilder(getContext(), sliceUri)
429                .setColor(0xffff4081)
430                .addRow(b -> b
431                        .setTitle("2 toggles")
432                        .setSubtitle("each supports two states")
433                        .addEndItem(new SliceAction(
434                                getBroadcastIntent(ACTION_TOAST, "first star toggled"),
435                                Icon.createWithResource(getContext(), R.drawable.toggle_star),
436                                "Toggle star", true /* isChecked */))
437                        .addEndItem(new SliceAction(
438                                getBroadcastIntent(ACTION_TOAST, "second star toggled"),
439                                Icon.createWithResource(getContext(), R.drawable.toggle_star),
440                                "Toggle star", false /* isChecked */)))
441                .build();
442    }
443
444    private Slice createWifiSlice(Uri sliceUri) {
445        // Get wifi state
446        WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
447        int wifiState = wifiManager.getWifiState();
448        boolean wifiEnabled = false;
449        String state;
450        switch (wifiState) {
451            case WifiManager.WIFI_STATE_DISABLED:
452            case WifiManager.WIFI_STATE_DISABLING:
453                state = "disconnected";
454                break;
455            case WifiManager.WIFI_STATE_ENABLED:
456            case WifiManager.WIFI_STATE_ENABLING:
457                state = wifiManager.getConnectionInfo().getSSID();
458                wifiEnabled = true;
459                break;
460            case WifiManager.WIFI_STATE_UNKNOWN:
461            default:
462                state = ""; // just don't show anything?
463                break;
464        }
465
466        // Set the first row as a toggle
467        boolean finalWifiEnabled = wifiEnabled;
468        SliceAction primaryAction = new SliceAction(getIntent(Settings.ACTION_WIFI_SETTINGS),
469                Icon.createWithResource(getContext(), R.drawable.ic_wifi), "Wi-fi Settings");
470        ListBuilder lb = new ListBuilder(getContext(), sliceUri)
471                .setColor(0xff4285f4)
472                .addRow(b -> b
473                    .setTitle("Wi-fi")
474                    .setSubtitle(state)
475                    .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED, null),
476                            "Toggle wifi", finalWifiEnabled))
477                    .setPrimaryAction(primaryAction));
478
479        // Add fake wifi networks
480        int[] wifiIcons = new int[] {R.drawable.ic_wifi_full, R.drawable.ic_wifi_low,
481                R.drawable.ic_wifi_fair};
482        for (int i = 0; i < 10; i++) {
483            final int iconId = wifiIcons[i % wifiIcons.length];
484            Icon icon = Icon.createWithResource(getContext(), iconId);
485            final String networkName = "Network" + i;
486            ListBuilder.RowBuilder rb = new ListBuilder.RowBuilder(lb);
487            rb.setTitleItem(icon, ICON_IMAGE).setTitle("Network" + networkName);
488            boolean locked = i % 3 == 0;
489            if (locked) {
490                rb.addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_lock));
491            }
492            String message = locked ? "Open wifi password dialog" : "Connect to " + networkName;
493            rb.setPrimaryAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, message), icon,
494                    message));
495            lb.addRow(rb);
496        }
497
498        // Add see more intent
499        if (TEST_CUSTOM_SEE_MORE) {
500            lb.addSeeMoreRow(rb -> rb
501                    .setTitle("See all available networks")
502                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_right_caret))
503                    .setPrimaryAction(primaryAction));
504        } else {
505            lb.addSeeMoreAction(primaryAction.getAction());
506        }
507        return lb.build();
508    }
509
510    private Slice createStarRatingInputRange(Uri sliceUri) {
511        return new ListBuilder(getContext(), sliceUri)
512                .setColor(0xffff4081)
513                .addInputRange(c -> c
514                        .setTitle("Star rating")
515                        .setThumb(Icon.createWithResource(getContext(), R.drawable.ic_star_on))
516                        .setAction(getBroadcastIntent(ACTION_TOAST_RANGE_VALUE, null))
517                        .setMax(5)
518                        .setValue(3))
519                .build();
520    }
521
522    private Slice createDownloadProgressRange(Uri sliceUri) {
523        return new ListBuilder(getContext(), sliceUri)
524                .setColor(0xffff4081)
525                .addRange(c -> c
526                        .setTitle("Download progress")
527                        .setMax(100)
528                        .setValue(75))
529                .build();
530    }
531
532    private Handler mHandler = new Handler();
533    private Runnable mLoader;
534    private boolean mLoaded = false;
535
536    private Slice createLoadingSlice(Uri sliceUri, boolean loadAll, boolean isList) {
537        if (!mLoaded || mLoader != null) {
538            // Need to load content or we're still loading so just return partial
539            if (!mLoaded) {
540                mLoader = () -> {
541                    // Note that we've loaded things
542                    mLoader = null;
543                    mLoaded = true;
544                    // Notify to update the slice
545                    getContext().getContentResolver().notifyChange(sliceUri, null);
546                };
547                mHandler.postDelayed(mLoader, LOADING_DELAY_MS);
548            }
549            if (loadAll) {
550                return new ListBuilder(getContext(), sliceUri).build();
551            }
552            return createPartialSlice(sliceUri, true, isList);
553        } else {
554            mLoaded = false;
555            return createPartialSlice(sliceUri, false, isList);
556        }
557    }
558
559    private Slice createPartialSlice(Uri sliceUri, boolean isPartial, boolean isList) {
560        Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_star_on);
561        PendingIntent intent = getBroadcastIntent(ACTION_TOAST, "star tapped");
562        PendingIntent intent2 = getBroadcastIntent(ACTION_TOAST, "toggle tapped");
563        if (isPartial) {
564            if (isList) {
565                return new ListBuilder(getContext(), sliceUri)
566                        .addRow(b -> createRow(b, "Slice that has content to load",
567                                "Temporary subtitle", icon, intent, true))
568                        .addRow(b -> createRow(b, null, null, null, intent, true))
569                        .addRow(b -> b
570                                .setTitle("My title")
571                                .addEndItem(new SliceAction(intent2, "Some action",
572                                        false /* isChecked */),
573                                        true /* isLoading */))
574                        .build();
575            } else {
576                return new ListBuilder(getContext(), sliceUri).addGrid(gb -> gb
577                        .addCell(b -> createCell(b, null, null, null, true))
578                        .addCell(b -> createCell(b, "Two stars", null, icon, true))
579                        .addCell(b -> createCell(b, null, null, null, true)))
580                        .build();
581            }
582        } else {
583            if (isList) {
584                return new ListBuilder(getContext(), sliceUri)
585                        .addRow(b -> createRow(b, "Slice that has content to load",
586                                "Subtitle loaded", icon, intent, false))
587                        .addRow(b -> createRow(b, "Loaded row", "Loaded subtitle",
588                                icon, intent, false))
589                        .addRow(b -> b
590                                .setTitle("My title")
591                                .addEndItem(new SliceAction(intent2, "Some action",
592                                                false /* isChecked */)))
593                        .build();
594            } else {
595                return new ListBuilder(getContext(), sliceUri).addGrid(gb -> gb
596                        .addCell(b -> createCell(b, "One star", "meh", icon, false))
597                        .addCell(b -> createCell(b, "Two stars", "good", icon, false))
598                        .addCell(b -> createCell(b, "Three stars", "best", icon, false)))
599                        .build();
600            }
601        }
602    }
603
604    private ListBuilder.RowBuilder createRow(ListBuilder.RowBuilder rb, String title,
605            String subtitle, Icon icon, PendingIntent content, boolean isLoading) {
606        SliceAction primaryAction = new SliceAction(content, icon, title);
607        return rb.setTitle(title, isLoading)
608          .setSubtitle(subtitle, isLoading)
609          .addEndItem(icon, isLoading)
610          .setPrimaryAction(primaryAction);
611    }
612
613    private GridBuilder.CellBuilder createCell(GridBuilder.CellBuilder cb, String text1,
614            String text2, Icon icon, boolean isLoading) {
615        return cb.addText(text1, isLoading).addText(text2, isLoading).addImage(icon,
616                SMALL_IMAGE, isLoading);
617    }
618
619    private PendingIntent getIntent(String action) {
620        Intent intent = new Intent(action);
621        PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
622        return pi;
623    }
624
625    private PendingIntent getBroadcastIntent(String action, String message) {
626        Intent intent = new Intent(action);
627        intent.setClass(getContext(), SliceBroadcastReceiver.class);
628        // Ensure a new PendingIntent is created for each message.
629        int requestCode = 0;
630        if (message != null) {
631            intent.putExtra(EXTRA_TOAST_MESSAGE, message);
632            requestCode = message.hashCode();
633        }
634        return PendingIntent.getBroadcast(getContext(), requestCode, intent,
635                PendingIntent.FLAG_UPDATE_CURRENT);
636    }
637}
638