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