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