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 17 package com.example.androidx.slice.demos; 18 19 import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; 20 21 import static androidx.slice.builders.ListBuilder.ICON_IMAGE; 22 import static androidx.slice.builders.ListBuilder.INFINITY; 23 import static androidx.slice.builders.ListBuilder.LARGE_IMAGE; 24 import static androidx.slice.builders.ListBuilder.SMALL_IMAGE; 25 26 import android.app.PendingIntent; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.net.Uri; 31 import android.net.wifi.WifiManager; 32 import android.os.Handler; 33 import android.provider.Settings; 34 import android.text.SpannableString; 35 import android.text.TextUtils; 36 import android.text.format.DateUtils; 37 import android.text.style.ForegroundColorSpan; 38 import android.util.SparseArray; 39 40 import androidx.annotation.NonNull; 41 import androidx.core.graphics.drawable.IconCompat; 42 import androidx.slice.Slice; 43 import androidx.slice.SliceProvider; 44 import androidx.slice.builders.GridRowBuilder; 45 import androidx.slice.builders.ListBuilder; 46 import androidx.slice.builders.MessagingSliceBuilder; 47 import androidx.slice.builders.SliceAction; 48 49 import java.util.Arrays; 50 import java.util.Calendar; 51 import java.util.concurrent.TimeUnit; 52 53 /** 54 * Examples of using slice template builders. 55 */ 56 public class SampleSliceProvider extends SliceProvider { 57 58 private static final boolean TEST_CUSTOM_SEE_MORE = false; 59 60 public static final String ACTION_WIFI_CHANGED = 61 "com.example.androidx.slice.action.WIFI_CHANGED"; 62 public static final String ACTION_TOAST = 63 "com.example.androidx.slice.action.TOAST"; 64 public static final String EXTRA_TOAST_MESSAGE = "com.example.androidx.extra.TOAST_MESSAGE"; 65 public static final String ACTION_TOAST_RANGE_VALUE = 66 "com.example.androidx.slice.action.TOAST_RANGE_VALUE"; 67 68 public static final String[] URI_PATHS = { 69 "message", 70 "wifi", 71 "note", 72 "ride", 73 "toggle", 74 "toggle2", 75 "toggletester", 76 "contact", 77 "contact2", 78 "gallery", 79 "gallery2", 80 "weather", 81 "reservation", 82 "loadlist", 83 "loadgrid", 84 "inputrange", 85 "range", 86 "subscription", 87 "singleitems", 88 }; 89 90 /** 91 * @return Uri with the provided path 92 */ 93 public static Uri getUri(String path, Context context) { 94 return new Uri.Builder() 95 .scheme(ContentResolver.SCHEME_CONTENT) 96 .authority(context.getPackageName()) 97 .appendPath(path) 98 .build(); 99 } 100 101 @Override 102 public boolean onCreateSliceProvider() { 103 return true; 104 } 105 106 @NonNull 107 @Override 108 public Uri onMapIntentToUri(Intent intent) { 109 return getUri("wifi", getContext()); 110 } 111 112 @Override 113 public Slice onBindSlice(Uri sliceUri) { 114 String path = sliceUri.getPath(); 115 if (!path.equals("/loadlist")) { 116 mListSummaries.clear(); 117 mListLastUpdate = 0; 118 } 119 if (!path.equals("/loadgrid")) { 120 mGridSummaries.clear(); 121 mGridLastUpdate = 0; 122 } 123 switch (path) { 124 // TODO: add list / grid slices with 'see more' options 125 case "/message": 126 return createMessagingSlice(sliceUri); 127 case "/wifi": 128 return createWifiSlice(sliceUri); 129 case "/note": 130 return createNoteSlice(sliceUri); 131 case "/ride": 132 return createRideSlice(sliceUri); 133 case "/toggle": 134 return createCustomToggleSlice(sliceUri); 135 case "/toggle2": 136 return createTwoCustomToggleSlices(sliceUri); 137 case "/toggletester": 138 return createdToggleTesterSlice(sliceUri); 139 case "/contact": 140 return createContact(sliceUri); 141 case "/contact2": 142 return createContact2(sliceUri); 143 case "/gallery": 144 return createGallery(sliceUri, true /* showHeader */); 145 case "/gallery2": 146 return createGallery(sliceUri, false /* showHeader */); 147 case "/weather": 148 return createWeather(sliceUri); 149 case "/reservation": 150 return createReservationSlice(sliceUri); 151 case "/loadlist": 152 return createLoadingListSlice(sliceUri); 153 case "/loadgrid": 154 return createLoadingGridSlice(sliceUri); 155 case "/inputrange": 156 return createStarRatingInputRange(sliceUri); 157 case "/range": 158 return createDownloadProgressRange(sliceUri); 159 case "/subscription": 160 return createCatSlice(sliceUri, false /* customSeeMore */); 161 case "/singleitems": 162 return createSingleSlice(sliceUri); 163 } 164 throw new IllegalArgumentException("Unknown uri " + sliceUri); 165 } 166 167 private Slice createWeather(Uri sliceUri) { 168 SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, 169 "open weather app"), 170 IconCompat.createWithResource(getContext(), R.drawable.weather_1), SMALL_IMAGE, 171 "Weather is happening!"); 172 return new ListBuilder(getContext(), sliceUri, INFINITY) 173 .addGridRow(gb -> gb 174 .setPrimaryAction(primaryAction) 175 .addCell(cb -> cb 176 .addImage(IconCompat.createWithResource(getContext(), 177 R.drawable.weather_1), 178 SMALL_IMAGE) 179 .addText("MON") 180 .addTitleText("69\u00B0")) 181 .addCell(cb -> cb 182 .addImage(IconCompat.createWithResource(getContext(), 183 R.drawable.weather_2), 184 SMALL_IMAGE) 185 .addText("TUE") 186 .addTitleText("71\u00B0")) 187 .addCell(cb -> cb 188 .addImage(IconCompat.createWithResource(getContext(), 189 R.drawable.weather_3), 190 SMALL_IMAGE) 191 .addText("WED") 192 .addTitleText("76\u00B0")) 193 .addCell(cb -> cb 194 .addImage(IconCompat.createWithResource(getContext(), 195 R.drawable.weather_4), 196 SMALL_IMAGE) 197 .addText("THU") 198 .addTitleText("72\u00B0")) 199 .addCell(cb -> cb 200 .addImage(IconCompat.createWithResource(getContext(), 201 R.drawable.weather_1), 202 SMALL_IMAGE) 203 .addText("FRI") 204 .addTitleText("68\u00B0"))) 205 .build(); 206 } 207 208 private Slice createGallery(Uri sliceUri, boolean showHeader) { 209 SliceAction primaryAction = new SliceAction( 210 getBroadcastIntent(ACTION_TOAST, "open photo album"), 211 IconCompat.createWithResource(getContext(), R.drawable.slices_1), 212 LARGE_IMAGE, 213 "Open photo album"); 214 ListBuilder lb = new ListBuilder(getContext(), sliceUri, INFINITY) 215 .setAccentColor(0xff4285F4); 216 if (showHeader) { 217 lb.addRow(b -> b 218 .setTitle("Family trip to Hawaii") 219 .setSubtitle("Sep 30, 2017 - Oct 2, 2017") 220 .setPrimaryAction(primaryAction)) 221 .addAction(new SliceAction( 222 getBroadcastIntent(ACTION_TOAST, "cast photo album"), 223 IconCompat.createWithResource(getContext(), R.drawable.ic_cast), 224 "Cast photo album")) 225 .addAction(new SliceAction( 226 getBroadcastIntent(ACTION_TOAST, "share photo album"), 227 IconCompat.createWithResource(getContext(), R.drawable.ic_share), 228 "Share photo album")); 229 } 230 int[] galleryResId = new int[] {R.drawable.slices_1, R.drawable.slices_2, 231 R.drawable.slices_3, R.drawable.slices_4}; 232 int imageCount = 7; 233 GridRowBuilder grb = new GridRowBuilder(lb); 234 for (int i = 0; i < imageCount; i++) { 235 IconCompat ic = IconCompat.createWithResource(getContext(), 236 galleryResId[i % galleryResId.length]); 237 grb.addCell(cb -> cb.addImage(ic, LARGE_IMAGE)); 238 } 239 grb.setPrimaryAction(primaryAction) 240 .setSeeMoreAction(getBroadcastIntent(ACTION_TOAST, "see your gallery")) 241 .setContentDescription("Images from your trip to Hawaii"); 242 return lb.addGridRow(grb).build(); 243 } 244 245 private Slice createCatSlice(Uri sliceUri, boolean customSeeMore) { 246 ListBuilder b = new ListBuilder(getContext(), sliceUri, INFINITY); 247 GridRowBuilder gb = new GridRowBuilder(b); 248 PendingIntent pi = getBroadcastIntent(ACTION_TOAST, "See cats you follow"); 249 if (customSeeMore) { 250 GridRowBuilder.CellBuilder cb = new GridRowBuilder.CellBuilder(gb); 251 cb.addImage(IconCompat.createWithResource(getContext(), R.drawable.ic_right_caret), 252 ICON_IMAGE); 253 cb.setContentIntent(pi); 254 cb.addTitleText("All cats"); 255 gb.setSeeMoreCell(cb); 256 } else { 257 gb.setSeeMoreAction(pi); 258 } 259 gb.addCell(new GridRowBuilder.CellBuilder(gb) 260 .addImage(IconCompat.createWithResource(getContext(), R.drawable.cat_1), 261 SMALL_IMAGE) 262 .addTitleText("Oreo")) 263 .addCell(new GridRowBuilder.CellBuilder(gb) 264 .addImage(IconCompat.createWithResource(getContext(), R.drawable.cat_2), 265 SMALL_IMAGE) 266 .addTitleText("Silver")) 267 .addCell(new GridRowBuilder.CellBuilder(gb) 268 .addImage(IconCompat.createWithResource(getContext(), R.drawable.cat_3), 269 SMALL_IMAGE) 270 .addTitleText("Drake")) 271 .addCell(new GridRowBuilder.CellBuilder(gb) 272 .addImage(IconCompat.createWithResource(getContext(), R.drawable.cat_5), 273 SMALL_IMAGE) 274 .addTitleText("Olive")) 275 .addCell(new GridRowBuilder.CellBuilder(gb) 276 .addImage(IconCompat.createWithResource(getContext(), R.drawable.cat_4), 277 SMALL_IMAGE) 278 .addTitleText("Lady Marmalade")) 279 .addCell(new GridRowBuilder.CellBuilder(gb) 280 .addImage(IconCompat.createWithResource(getContext(), R.drawable.cat_6), 281 SMALL_IMAGE) 282 .addTitleText("Grapefruit")); 283 return b.addGridRow(gb).build(); 284 } 285 286 private Slice createContact2(Uri sliceUri) { 287 ListBuilder b = new ListBuilder(getContext(), sliceUri, INFINITY); 288 ListBuilder.RowBuilder rb = new ListBuilder.RowBuilder(b); 289 GridRowBuilder gb = new GridRowBuilder(b); 290 return b.setAccentColor(0xff3949ab) 291 .addRow(rb 292 .setTitle("Mady Pitza") 293 .setSubtitle("Frequently contacted contact") 294 .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.mady), 295 SMALL_IMAGE)) 296 .addGridRow(gb 297 .addCell(new GridRowBuilder.CellBuilder(gb) 298 .addImage(IconCompat.createWithResource(getContext(), 299 R.drawable.ic_call), 300 ICON_IMAGE) 301 .addText("Call") 302 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "call"))) 303 .addCell(new GridRowBuilder.CellBuilder(gb) 304 .addImage(IconCompat.createWithResource(getContext(), 305 R.drawable.ic_text), 306 ICON_IMAGE) 307 .addText("Text") 308 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "text"))) 309 .addCell(new GridRowBuilder.CellBuilder(gb) 310 .addImage(IconCompat.createWithResource(getContext(), 311 R.drawable.ic_video), ICON_IMAGE) 312 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "video")) 313 .addText("Video")) 314 .addCell(new GridRowBuilder.CellBuilder(gb) 315 .addImage(IconCompat.createWithResource(getContext(), 316 R.drawable.ic_email), ICON_IMAGE) 317 .addText("Email") 318 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "email")))) 319 .build(); 320 } 321 322 private Slice createContact(Uri sliceUri) { 323 final long lastCalled = System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS; 324 CharSequence lastCalledString = DateUtils.getRelativeTimeSpanString(lastCalled, 325 Calendar.getInstance().getTimeInMillis(), 326 DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); 327 SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, 328 "See contact info"), IconCompat.createWithResource(getContext(), 329 R.drawable.mady), SMALL_IMAGE, "Mady"); 330 331 return new ListBuilder(getContext(), sliceUri, INFINITY) 332 .setAccentColor(0xff3949ab) 333 .setHeader(b -> b 334 .setTitle("Mady Pitza") 335 .setSummary("Called " + lastCalledString) 336 .setPrimaryAction(primaryAction)) 337 .addRow(b -> b 338 .setTitleItem( 339 IconCompat.createWithResource(getContext(), R.drawable.ic_call), 340 ICON_IMAGE) 341 .setTitle("314-259-2653") 342 .setSubtitle("Call lasted 1 hr 17 min") 343 .addEndItem(lastCalled)) 344 .addRow(b -> b 345 .setTitleItem( 346 IconCompat.createWithResource(getContext(), R.drawable.ic_text), 347 ICON_IMAGE) 348 .setTitle("You: Coooooool see you then") 349 .addEndItem(System.currentTimeMillis() - 40 * DateUtils.MINUTE_IN_MILLIS)) 350 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "call"), 351 IconCompat.createWithResource(getContext(), R.drawable.ic_call), 352 "Call mady")) 353 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "text"), 354 IconCompat.createWithResource(getContext(), R.drawable.ic_text), 355 "Text mady")) 356 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "video"), 357 IconCompat.createWithResource(getContext(), R.drawable.ic_video), 358 "Video call mady")) 359 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "email"), 360 IconCompat.createWithResource(getContext(), R.drawable.ic_email), 361 "Email mady")) 362 .build(); 363 } 364 365 private Slice createMessagingSlice(Uri sliceUri) { 366 // TODO: Remote input. 367 return new MessagingSliceBuilder(getContext(), sliceUri) 368 .add(b -> b 369 .addText("yo home \uD83C\uDF55, I emailed you the info") 370 .addTimestamp(System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS) 371 .addSource(IconCompat.createWithResource(getContext(), R.drawable.mady))) 372 .add(b -> b 373 .addText("just bought my tickets") 374 .addTimestamp(System.currentTimeMillis() - 10 * DateUtils.MINUTE_IN_MILLIS)) 375 .add(b -> b 376 .addText("yay! can't wait for getContext() weekend!\n" 377 + "\uD83D\uDE00") 378 .addTimestamp(System.currentTimeMillis() - 5 * DateUtils.MINUTE_IN_MILLIS) 379 .addSource(IconCompat.createWithResource(getContext(), R.drawable.mady))) 380 .build(); 381 382 } 383 384 private Slice createNoteSlice(Uri sliceUri) { 385 // TODO: Remote input. 386 return new ListBuilder(getContext(), sliceUri, INFINITY) 387 .setAccentColor(0xfff4b400) 388 .addRow(b -> b.setTitle("Create new note")) 389 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "create note"), 390 IconCompat.createWithResource(getContext(), R.drawable.ic_create), 391 "Create note")) 392 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "voice note"), 393 IconCompat.createWithResource(getContext(), R.drawable.ic_voice), 394 "Voice note")) 395 .addAction(new SliceAction(getIntent("android.media.action.IMAGE_CAPTURE"), 396 IconCompat.createWithResource(getContext(), R.drawable.ic_camera), 397 "Photo note")) 398 .build(); 399 } 400 401 private Slice createReservationSlice(Uri sliceUri) { 402 return new ListBuilder(getContext(), sliceUri, INFINITY) 403 .setAccentColor(0xffFF5252) 404 .setHeader(b -> b 405 .setTitle("Upcoming trip to Seattle") 406 .setSubtitle("Feb 1 - 19 | 2 guests")) 407 .addAction(new SliceAction( 408 getBroadcastIntent(ACTION_TOAST, "show location on map"), 409 IconCompat.createWithResource(getContext(), R.drawable.ic_location), 410 "Show reservation location")) 411 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "contact host"), 412 IconCompat.createWithResource(getContext(), R.drawable.ic_text), 413 "Contact host")) 414 .addGridRow(b -> b 415 .addCell(cb -> cb 416 .addImage(IconCompat.createWithResource(getContext(), 417 R.drawable.reservation), 418 LARGE_IMAGE) 419 .setContentDescription("Image of your reservation in Seattle"))) 420 .addGridRow(b -> b 421 .addCell(cb -> cb 422 .addTitleText("Check In") 423 .addText("12:00 PM, Feb 1")) 424 .addCell(cb -> cb 425 .addTitleText("Check Out") 426 .addText("11:00 AM, Feb 19"))) 427 .build(); 428 } 429 430 private Slice createRideSlice(Uri sliceUri) { 431 final ForegroundColorSpan colorSpan = new ForegroundColorSpan(0xff0F9D58); 432 SpannableString headerSubtitle = new SpannableString("Ride in 4 min"); 433 headerSubtitle.setSpan(colorSpan, 8, headerSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); 434 SpannableString homeSubtitle = new SpannableString("12 miles | 12 min | $9.00"); 435 homeSubtitle.setSpan(colorSpan, 20, homeSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); 436 SpannableString workSubtitle = new SpannableString("44 miles | 1 hour 45 min | $31.41"); 437 workSubtitle.setSpan(colorSpan, 27, workSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); 438 439 SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, "get ride"), 440 IconCompat.createWithResource(getContext(), R.drawable.ic_car), "Get Ride"); 441 return new ListBuilder(getContext(), sliceUri, TimeUnit.SECONDS.toMillis(10)) 442 .setAccentColor(0xff0F9D58) 443 .setHeader(b -> b 444 .setTitle("Get ride") 445 .setSubtitle(headerSubtitle) 446 .setSummary("Ride to work in 12 min | Ride home in 1 hour 45 min") 447 .setPrimaryAction(primaryAction)) 448 .addRow(b -> b 449 .setTitle("Work") 450 .setSubtitle(workSubtitle) 451 .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "work"), 452 IconCompat.createWithResource(getContext(), R.drawable.ic_work), 453 "Get ride to work"))) 454 .addRow(b -> b 455 .setTitle("Home") 456 .setSubtitle(homeSubtitle) 457 .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "home"), 458 IconCompat.createWithResource(getContext(), R.drawable.ic_home), 459 "Get ride home"))) 460 .build(); 461 } 462 463 private Slice createCustomToggleSlice(Uri sliceUri) { 464 return new ListBuilder(getContext(), sliceUri, INFINITY) 465 .setAccentColor(0xffff4081) 466 .addRow(b -> b 467 .setTitle("Custom toggle") 468 .setSubtitle("It can support two states") 469 .setPrimaryAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, 470 "star toggled"), 471 IconCompat.createWithResource(getContext(), R.drawable.toggle_star), 472 "Toggle star", true /* isChecked */))) 473 .build(); 474 } 475 476 private Slice createTwoCustomToggleSlices(Uri sliceUri) { 477 return new ListBuilder(getContext(), sliceUri, INFINITY) 478 .setAccentColor(0xffff4081) 479 .addRow(b -> b 480 .setTitle("2 toggles") 481 .setSubtitle("each supports two states") 482 .addEndItem(new SliceAction( 483 getBroadcastIntent(ACTION_TOAST, "first star toggled"), 484 IconCompat.createWithResource(getContext(), R.drawable.toggle_star), 485 "Toggle star", true /* isChecked */)) 486 .addEndItem(new SliceAction( 487 getBroadcastIntent(ACTION_TOAST, "second star toggled"), 488 IconCompat.createWithResource(getContext(), R.drawable.toggle_star), 489 "Toggle star", false /* isChecked */))) 490 .build(); 491 } 492 493 private Slice createWifiSlice(Uri sliceUri) { 494 // Get wifi state 495 WifiManager wifiManager = (WifiManager) getContext() 496 .getApplicationContext().getSystemService(Context.WIFI_SERVICE); 497 int wifiState = wifiManager.getWifiState(); 498 boolean wifiEnabled = false; 499 String state; 500 switch (wifiState) { 501 case WifiManager.WIFI_STATE_DISABLED: 502 case WifiManager.WIFI_STATE_DISABLING: 503 state = "disconnected"; 504 break; 505 case WifiManager.WIFI_STATE_ENABLED: 506 case WifiManager.WIFI_STATE_ENABLING: 507 state = wifiManager.getConnectionInfo().getSSID(); 508 wifiEnabled = true; 509 break; 510 case WifiManager.WIFI_STATE_UNKNOWN: 511 default: 512 state = ""; // just don't show anything? 513 break; 514 } 515 516 // Set the first row as a toggle 517 boolean finalWifiEnabled = wifiEnabled; 518 SliceAction primaryAction = new SliceAction(getIntent(Settings.ACTION_WIFI_SETTINGS), 519 IconCompat.createWithResource(getContext(), R.drawable.ic_wifi), "Wi-fi Settings"); 520 String toggleCDString = wifiEnabled ? "Turn wifi off" : "Turn wifi on"; 521 String sliceCDString = wifiEnabled ? "Wifi connected to " + state 522 : "Wifi disconnected, 10 networks available"; 523 ListBuilder lb = new ListBuilder(getContext(), sliceUri, INFINITY) 524 .setAccentColor(0xff4285f4) 525 .setHeader(b -> b 526 .setTitle("Wi-fi") 527 .setSubtitle(state) 528 .setContentDescription(sliceCDString) 529 .setPrimaryAction(primaryAction)) 530 .addAction((new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED, null), 531 toggleCDString, finalWifiEnabled))); 532 533 // Add fake wifi networks 534 int[] wifiIcons = new int[]{R.drawable.ic_wifi_full, R.drawable.ic_wifi_low, 535 R.drawable.ic_wifi_fair}; 536 for (int i = 0; i < 10; i++) { 537 final int iconId = wifiIcons[i % wifiIcons.length]; 538 IconCompat icon = IconCompat.createWithResource(getContext(), iconId); 539 final String networkName = "Network" + i; 540 ListBuilder.RowBuilder rb = new ListBuilder.RowBuilder(lb); 541 rb.setTitleItem(icon, ICON_IMAGE).setTitle(networkName); 542 boolean locked = i % 3 == 0; 543 if (locked) { 544 rb.addEndItem(IconCompat.createWithResource(getContext(), R.drawable.ic_lock), 545 ICON_IMAGE); 546 rb.setContentDescription("Connect to " + networkName + ", password needed"); 547 } else { 548 rb.setContentDescription("Connect to " + networkName); 549 } 550 String message = locked ? "Open wifi password dialog" : "Connect to " + networkName; 551 rb.setPrimaryAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, message), icon, 552 message)); 553 lb.addRow(rb); 554 } 555 556 // Add keywords 557 String[] keywords = new String[]{"internet", "wifi", "data", "network"}; 558 lb.setKeywords(Arrays.asList(keywords)); 559 560 // Add see more intent 561 if (TEST_CUSTOM_SEE_MORE) { 562 lb.setSeeMoreRow(rb -> rb 563 .setTitle("See all available networks") 564 .addEndItem( 565 IconCompat.createWithResource(getContext(), R.drawable.ic_right_caret), 566 SMALL_IMAGE) 567 .setPrimaryAction(primaryAction)); 568 } else { 569 lb.setSeeMoreAction(primaryAction.getAction()); 570 } 571 return lb.build(); 572 } 573 574 private Slice createStarRatingInputRange(Uri sliceUri) { 575 IconCompat icon = IconCompat.createWithResource(getContext(), R.drawable.ic_star_on); 576 SliceAction primaryAction = 577 new SliceAction(getBroadcastIntent(ACTION_TOAST, "open star rating"), 578 icon, "Rate"); 579 return new ListBuilder(getContext(), sliceUri, INFINITY) 580 .setAccentColor(0xffff4081) 581 .addInputRange(c -> c 582 .setTitle("Star rating") 583 .setSubtitle("Rate from 5 to 10 because it's weird") 584 .setMin(5) 585 .setThumb(icon) 586 .setInputAction(getBroadcastIntent(ACTION_TOAST_RANGE_VALUE, null)) 587 .setMax(10) 588 .setValue(8) 589 .setPrimaryAction(primaryAction) 590 .setContentDescription("Slider for star ratings")) 591 .build(); 592 } 593 594 private Slice createDownloadProgressRange(Uri sliceUri) { 595 IconCompat icon = IconCompat.createWithResource(getContext(), R.drawable.ic_star_on); 596 SliceAction primaryAction = 597 new SliceAction( 598 getBroadcastIntent(ACTION_TOAST, "open download"), icon, "Download"); 599 return new ListBuilder(getContext(), sliceUri, INFINITY) 600 .setAccentColor(0xffff4081) 601 .addRange(c -> c 602 .setTitle("Download progress") 603 .setSubtitle("Download is happening") 604 .setMax(100) 605 .setValue(75) 606 .setPrimaryAction(primaryAction)) 607 .build(); 608 } 609 610 private Slice createdToggleTesterSlice(Uri uri) { 611 IconCompat star = IconCompat.createWithResource(getContext(), R.drawable.toggle_star); 612 IconCompat icon = IconCompat.createWithResource(getContext(), R.drawable.ic_star_on); 613 614 SliceAction primaryAction = new SliceAction( 615 getBroadcastIntent(ACTION_TOAST, "primary action"), icon, "Primary action"); 616 SliceAction toggleAction = new SliceAction( 617 getBroadcastIntent(ACTION_TOAST, "star note"), star, "Star note", false); 618 SliceAction toggleAction2 = new SliceAction( 619 getBroadcastIntent(ACTION_TOAST, "star note 2"), star, "Star note 2", true); 620 SliceAction toggleAction3 = new SliceAction( 621 getBroadcastIntent(ACTION_TOAST, "star note 3"), star, "Star note 3", false); 622 623 ListBuilder lb = new ListBuilder(getContext(), uri, INFINITY); 624 625 // Primary action toggle 626 ListBuilder.RowBuilder primaryToggle = new ListBuilder.RowBuilder(lb); 627 primaryToggle.setTitle("Primary action is a toggle") 628 .setPrimaryAction(toggleAction); 629 630 // End toggle + normal primary action 631 ListBuilder.RowBuilder endToggle = new ListBuilder.RowBuilder(lb); 632 endToggle.setTitle("Only end toggles") 633 .setSubtitle("Normal primary action") 634 .setPrimaryAction(primaryAction) 635 .addEndItem(toggleAction) 636 .addEndItem(toggleAction2); 637 638 // Start toggle + normal primary 639 ListBuilder.RowBuilder startToggle = new ListBuilder.RowBuilder(lb); 640 startToggle.setTitle("One start toggle") 641 .setTitleItem(toggleAction) 642 .setSubtitle("Normal primary action") 643 .setPrimaryAction(primaryAction); 644 645 // Start + end toggles + normal primary action 646 ListBuilder.RowBuilder someToggles = new ListBuilder.RowBuilder(lb); 647 someToggles.setTitleItem(toggleAction) 648 .setPrimaryAction(primaryAction) 649 .setTitle("Start & end toggles") 650 .setSubtitle("Normal primary action") 651 .addEndItem(toggleAction2) 652 .addEndItem(toggleAction3); 653 654 // Start toggle ONLY 655 ListBuilder.RowBuilder startToggleOnly = new ListBuilder.RowBuilder(lb); 656 startToggleOnly.setTitle("Start action is a toggle") 657 .setSubtitle("No other actions") 658 .setTitleItem(toggleAction); 659 660 // End toggle ONLY 661 ListBuilder.RowBuilder endToggleOnly = new ListBuilder.RowBuilder(lb); 662 endToggleOnly.setTitle("End action is a toggle") 663 .setSubtitle("No other actions") 664 .addEndItem(toggleAction); 665 666 // All toggles: end item should be ignored / replaced with primary action 667 ListBuilder.RowBuilder muchToggles = new ListBuilder.RowBuilder(lb); 668 muchToggles.setTitleItem(toggleAction) 669 .setTitle("All toggles") 670 .setSubtitle("Even the primary action") 671 .setPrimaryAction(toggleAction2) 672 .addEndItem(toggleAction3); 673 674 lb.addRow(primaryToggle); 675 lb.addRow(endToggleOnly); 676 lb.addRow(endToggle); 677 lb.addRow(startToggleOnly); 678 lb.addRow(startToggle); 679 lb.addRow(someToggles); 680 lb.addRow(muchToggles); 681 return lb.build(); 682 } 683 684 private Slice createSingleSlice(Uri uri) { 685 IconCompat ic2 = IconCompat.createWithResource(getContext(), R.drawable.ic_create); 686 IconCompat image = IconCompat.createWithResource(getContext(), R.drawable.cat_3); 687 IconCompat toggle = IconCompat.createWithResource(getContext(), R.drawable.toggle_star); 688 SliceAction toggleAction = new SliceAction( 689 getBroadcastIntent(ACTION_TOAST, "toggle action"), toggle, "toggle", false); 690 SliceAction simpleAction = new SliceAction( 691 getBroadcastIntent(ACTION_TOAST, "icon action"), ic2, "icon"); 692 ListBuilder lb = new ListBuilder(getContext(), uri, INFINITY); 693 return lb.addRow(new ListBuilder.RowBuilder(lb) 694 .setTitle("Single title")) 695 .addRow(new ListBuilder.RowBuilder(lb) 696 .setSubtitle("Single subtitle")) 697 //Time stamps 698 .addRow(new ListBuilder.RowBuilder(lb) 699 .setTitleItem(System.currentTimeMillis())) 700 .addRow(new ListBuilder.RowBuilder(lb) 701 .addEndItem(System.currentTimeMillis())) 702 // Toggle actions 703 .addRow(new ListBuilder.RowBuilder(lb) 704 .setTitleItem(toggleAction)) 705 .addRow(new ListBuilder.RowBuilder(lb) 706 .addEndItem(toggleAction)) 707 // Icon actions 708 .addRow(new ListBuilder.RowBuilder(lb) 709 .setTitleItem(simpleAction)) 710 .addRow(new ListBuilder.RowBuilder(lb) 711 .addEndItem(simpleAction)) 712 // Images 713 .addRow(new ListBuilder.RowBuilder(lb) 714 .setTitleItem(image, SMALL_IMAGE)) 715 .addRow(new ListBuilder.RowBuilder(lb) 716 .addEndItem(image, SMALL_IMAGE)) 717 .build(); 718 } 719 720 private Handler mHandler = new Handler(); 721 private SparseArray<String> mListSummaries = new SparseArray<>(); 722 private long mListLastUpdate; 723 private SparseArray<String> mGridSummaries = new SparseArray<>(); 724 private long mGridLastUpdate; 725 726 private void update(long delay, SparseArray<String> summaries, int id, String s, Uri uri, 727 Runnable r) { 728 mHandler.postDelayed(() -> { 729 summaries.put(id, s); 730 getContext().getContentResolver().notifyChange(uri, null); 731 r.run(); 732 }, delay); 733 } 734 735 private Slice createLoadingListSlice(Uri sliceUri) { 736 boolean updating = mListLastUpdate == 0 737 || mListLastUpdate < (System.currentTimeMillis() - 10 * System.currentTimeMillis()); 738 if (updating) { 739 Runnable r = () -> mListLastUpdate = System.currentTimeMillis(); 740 update(1000, mListSummaries, 0, "44 miles | 1 hour 45 min | $31.41", sliceUri, r); 741 update(1500, mListSummaries, 1, "12 miles | 12 min | $9.00", sliceUri, r); 742 update(1700, mListSummaries, 2, "5 miles | 10 min | $8.00", sliceUri, r); 743 } 744 CharSequence work = mListSummaries.get(0, ""); 745 CharSequence home = mListSummaries.get(1, ""); 746 CharSequence school = mListSummaries.get(2, ""); 747 Slice s = new ListBuilder(getContext(), sliceUri, -TimeUnit.MINUTES.toMillis(50)) 748 .addRow(b -> b 749 .setTitle("Work") 750 .setSubtitle(work, 751 updating || TextUtils.isEmpty(work)) 752 .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.ic_work), 753 ICON_IMAGE)) 754 .addRow(b -> b 755 .setTitle("Home") 756 .setSubtitle(mListSummaries.get(1, ""), 757 updating || TextUtils.isEmpty(home)) 758 .addEndItem( 759 IconCompat.createWithResource(getContext(), R.drawable.ic_home), 760 ICON_IMAGE)) 761 .addRow(b -> b 762 .setTitle("School") 763 .setSubtitle(mListSummaries.get(2, ""), 764 updating || TextUtils.isEmpty(school)) 765 .addEndItem( 766 IconCompat.createWithResource(getContext(), R.drawable.ic_school), 767 ICON_IMAGE)) 768 .build(); 769 return s; 770 } 771 772 // TODO: Should test large image grids 773 private Slice createLoadingGridSlice(Uri sliceUri) { 774 boolean updating = mGridLastUpdate == 0 775 || mGridLastUpdate < (System.currentTimeMillis() - 10 * System.currentTimeMillis()); 776 if (updating) { 777 Runnable r = () -> mGridLastUpdate = System.currentTimeMillis(); 778 update(2000, mGridSummaries, 0, "Heavy traffic in your area", sliceUri, r); 779 update(3500, mGridSummaries, 1, "Typical conditions with delays up to 28 min", 780 sliceUri, r); 781 update(3000, mGridSummaries, 2, "41 min", sliceUri, r); 782 update(1500, mGridSummaries, 3, "33 min", sliceUri, r); 783 update(1000, mGridSummaries, 4, "12 min", sliceUri, r); 784 } 785 CharSequence title = mGridSummaries.get(0, ""); 786 CharSequence subtitle = mGridSummaries.get(1, ""); 787 CharSequence home = mGridSummaries.get(2, ""); 788 CharSequence work = mGridSummaries.get(3, ""); 789 CharSequence school = mGridSummaries.get(4, ""); 790 Slice s = new ListBuilder(getContext(), sliceUri, INFINITY) 791 .setHeader(hb -> hb 792 .setTitle(title, 793 updating || TextUtils.isEmpty(title)) 794 .setSubtitle(subtitle, 795 updating || TextUtils.isEmpty(subtitle))) 796 .addGridRow(gb -> gb 797 .addCell(cb -> cb 798 .addImage(IconCompat.createWithResource(getContext(), 799 R.drawable.ic_home), 800 ICON_IMAGE) 801 .addTitleText("Home") 802 .addText(home, 803 updating || TextUtils.isEmpty(home))) 804 .addCell(cb -> cb 805 .addImage(IconCompat.createWithResource(getContext(), 806 R.drawable.ic_work), 807 ICON_IMAGE) 808 .addTitleText("Work") 809 .addText(work, 810 updating || TextUtils.isEmpty(work))) 811 .addCell(cb -> cb 812 .addImage(IconCompat.createWithResource(getContext(), 813 R.drawable.ic_school), 814 ICON_IMAGE) 815 .addTitleText("School") 816 .addText(school, 817 updating || TextUtils.isEmpty(school)))) 818 .build(); 819 return s; 820 } 821 822 private PendingIntent getIntent(String action) { 823 Intent intent = new Intent(action); 824 PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0); 825 return pi; 826 } 827 828 private PendingIntent getBroadcastIntent(String action, String message) { 829 Intent intent = new Intent(action); 830 intent.setClass(getContext(), SliceBroadcastReceiver.class); 831 // Ensure a new PendingIntent is created for each message. 832 int requestCode = 0; 833 if (message != null) { 834 intent.putExtra(EXTRA_TOAST_MESSAGE, message); 835 requestCode = message.hashCode(); 836 } 837 return PendingIntent.getBroadcast(getContext(), requestCode, intent, 838 PendingIntent.FLAG_UPDATE_CURRENT); 839 } 840 } 841