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