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