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