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