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