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