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