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