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